Jekyll2023-06-15T02:57:11+00:00https://trevorwang.github.io/feed.xmlTrevor’s BlogA Fullstack engineer </br> 10 years for mobile application development including Android & iOS platforms </br> Having knowledges with Ruby, Nodejs, Java, Kotin, Swift etc.
Trevor WangFlutter 1.7.8 稳定版本发布2019-07-09T00:00:00+00:002019-07-09T00:00:00+00:00https://trevorwang.github.io/2019/07/09/flutter-1.7.8<p>Flutter 1.7.8 最新稳定版发布了, 最主要修复了在<a href="https://github.com/flutter/flutter/issues/18494">同一 APK 同时支持 32 位和 64 位的bug</a>, 这样就不用 使用这种黑科技来打包支持国内市场了,真香!!</p>
<p>修复的其他一些 bug 列表(部分)</p>
<h3 id="v176">v1.7.6</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/34684">#34684</a> The return type of the almost never used <code class="language-plaintext highlighter-rouge">ParentDataWidget.debugDescribeInvalidAncestorChain</code> method is now <code class="language-plaintext highlighter-rouge">Iterable<DiagnosticsNode></code> instead of <code class="language-plaintext highlighter-rouge">String</code>. Existing uses can call <code class="language-plaintext highlighter-rouge">.join('\n')</code> on the return value if they need to continue to use a <code class="language-plaintext highlighter-rouge">String</code> instead of a <code class="language-plaintext highlighter-rouge">DiagnosticsNode</code>.</li>
</ul>
<h3 id="v174">v1.7.4</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/30069">#30069</a> Text inline widgets allows embedding arbitrary widgets inline with text. <code class="language-plaintext highlighter-rouge">TextSpan</code>has also been reworked to inherit from <code class="language-plaintext highlighter-rouge">InlineSpan</code>, in order to support <code class="language-plaintext highlighter-rouge">WidgetSpan</code>, which is used to define inline widgets. Existing uses of <code class="language-plaintext highlighter-rouge">TextSpan</code> should not assume the object is of type <code class="language-plaintext highlighter-rouge">TextSpan</code> anymore.</li>
</ul>
<h3 id="v171">v1.7.1</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/issues/33480">#33480</a> The <code class="language-plaintext highlighter-rouge">FadeInImage</code> widget no longer supports the <code class="language-plaintext highlighter-rouge">placeholderSemanticLabel</code>property.</li>
</ul>
<h3 id="v167">v1.6.7</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/issues/32374">#32374</a> The <code class="language-plaintext highlighter-rouge">Image</code> widget now supports two new properties: <code class="language-plaintext highlighter-rouge">frameBuilder</code>, and <code class="language-plaintext highlighter-rouge">loadingBuilder</code>, which enable callers to easily add a placeholder image which the image ios loading, add effects to the image once it loads (such as fading it in), and monitor the loading progress of the image (e.g. to show the user a loading indicator).</li>
</ul>
<h3 id="v163">v1.6.3</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/33148">#33148</a> ExpandIcon now uses Colors.white60 instead of Colors.white54 when dark theme is used (ThemeData.brightness is set to Brightness.dark).</li>
</ul>
<h3 id="v162">v1.6.2</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/32936">#32936</a> cleaned up the <code class="language-plaintext highlighter-rouge">ImageStream</code> listener API. <code class="language-plaintext highlighter-rouge">addListener()</code> and <code class="language-plaintext highlighter-rouge">removeListener()</code>now take an instance of <code class="language-plaintext highlighter-rouge">ImageStreamListener</code>, which contains references to the individual callbacks. See <a href="https://groups.google.com/forum/#!topic/flutter-announce/NWTszrEq9U0">the announcement</a> for more information.</li>
</ul>
<h3 id="v161">v1.6.1</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/32528">#32528</a> Tapping a modal bottom sheet no longer dismisses it by default.</li>
</ul>
<h3 id="v160">v1.6.0</h3>
<ul>
<li><a href="https://github.com/flutter/flutter/pull/30983">#30983</a> To support structured error messages <a href="https://github.com/flutter/flutter/issues/27327">#27327</a> the signature of InformationCollector and the context parameter of the FlutterErrorDetails constructor were changed. This should not impact you unless your package is attempting behave like part of the Flutter framework. See <a href="https://github.com/flutter/flutter/issues/31962#issuecomment-488882515">this comment</a> which discusses how to write code that is forwards and backwards compatible with this change.</li>
<li><a href="https://github.com/flutter/flutter/pull/31569">framework #31569</a>/<a href="https://github.com/flutter/engine/pull/8695">engine #8695</a> Rect and RRect are now const constructable and are backed by 64 bit doubles rather than 32 bit floats internally.</li>
<li><a href="https://github.com/flutter/flutter/pull/31917">framework #31917</a> <- <a href="https://github.com/flutter/engine/pull/8796">engine #8796</a> <- <a href="https://github.com/dart-lang/sdk/commit/1dd0f88c84">dart 1dd0f88c84</a> Fixes bug in StreamIterator which allowed constructor argument to be null. Also allowed await for on a null stream. This is now a runtime error.</li>
<li><a href="https://github.com/flutter/flutter/pull/30884">#30884</a> <code class="language-plaintext highlighter-rouge">TabController.animationController</code> changed to be <code class="language-plaintext highlighter-rouge">unbounded</code> to allow <code class="language-plaintext highlighter-rouge">DefaultTabController</code> to handle changes to number of tabs (after initial build).</li>
</ul>
<p>###</p>Trevor WangFlutter 1.7.8 最新稳定版发布了, 最主要修复了在同一 APK 同时支持 32 位和 64 位的bug, 这样就不用 使用这种黑科技来打包支持国内市场了,真香!!使用 Jacoco 检查测试覆盖率2019-06-20T00:00:00+00:002019-06-20T00:00:00+00:00https://trevorwang.github.io/2019/06/20/enable-jacoco-for-manual-test<p> 团队中目前还没有自动化测试的覆盖,所以测试 team 想了解下手动测试的覆盖率。于是才有了本片文章的产生。网上有很多文章是利用 Android 的 instrument 测试框架,然后通过命令来启动app来进行测试。而且报告生产的时间点是在启动的 activity 结束以后,在复杂场景下,是没有办法来捕捉到所有页面的函数调用的。</p>
<p>本文中的方案是对一个新的 <code class="language-plaintext highlighter-rouge">build type</code> 来重载 Application 代码,只在手动测试时候使用,对原来的代码不会产生任何影响。 希望可以帮到你。本文的实例代码可以到这里下载<a href="https://github.com/trevorwang/jacoco-manual-coverage">trevorwang/jacoco-manual-coverage</a></p>
<ol>
<li>
<p>在你的工程目录的<code class="language-plaintext highlighter-rouge">buildscripts</code> 下,新建一个 <code class="language-plaintext highlighter-rouge">jacoco.gradle</code> 的文件,添加如下代码</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">apply</span> <span class="nl">plugin:</span> <span class="s1">'jacoco'</span> <span class="c1">// 开启 jacoco</span>
<span class="n">jacoco</span> <span class="o">{</span>
<span class="n">toolVersion</span> <span class="o">=</span> <span class="s2">"0.8.3"</span> <span class="c1">// 设置 jacoco 版本号</span>
<span class="o">}</span>
<span class="c1">// 如果使用了 Robolectric 请务必添加如下代码</span>
<span class="n">tasks</span><span class="o">.</span><span class="na">withType</span><span class="o">(</span><span class="n">Test</span><span class="o">)</span> <span class="o">{</span>
<span class="n">jacoco</span><span class="o">.</span><span class="na">includeNoLocationClasses</span> <span class="o">=</span> <span class="kc">true</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>在 app 目录下的 <code class="language-plaintext highlighter-rouge">build.gradle</code> 中添加代码,来启用脚本</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">apply</span> <span class="nl">from:</span> <span class="s1">'../buildscripts/jacoco.gradle'</span>
</code></pre></div> </div>
</li>
<li>
<p>执行 <code class="language-plaintext highlighter-rouge">testDebugUnitTest</code> 后,会在<code class="language-plaintext highlighter-rouge">app/build/jococo/</code> 下看到 <code class="language-plaintext highlighter-rouge">testDebugUnitTest.exec</code>。 记住这个文件 我们会在后面用这个文件来生产报告。</p>
<p><img src="/assets/images/jacoco-executedata.png" alt="jacoco-executedata" /></p>
</li>
<li>
<p>创建 <code class="language-plaintext highlighter-rouge">jacoco</code> 任务</p>
<p>Android gradle plugin 会生成不同的 <code class="language-plaintext highlighter-rouge">variant</code>, 所以我们要对不用的 <code class="language-plaintext highlighter-rouge">variant</code> 生成不用的任务来生产报告。</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">project</span><span class="o">.</span><span class="na">afterEvaluate</span> <span class="o">{</span>
<span class="n">android</span><span class="o">.</span><span class="na">applicationVariants</span><span class="o">.</span><span class="na">all</span> <span class="o">{</span> <span class="n">variant</span> <span class="o">-></span>
<span class="kt">def</span> <span class="n">variantName</span> <span class="o">=</span> <span class="n">variant</span><span class="o">.</span><span class="na">name</span>
<span class="kt">def</span> <span class="n">testTaskName</span> <span class="o">=</span> <span class="s2">"test${variantName.capitalize()}UnitTest"</span>
<span class="n">tasks</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="nl">name:</span> <span class="s2">"${testTaskName}Coverage"</span><span class="o">,</span> <span class="nl">type:</span> <span class="n">JacocoReport</span><span class="o">,</span> <span class="nl">dependsOn:</span> <span class="s2">"$testTaskName"</span><span class="o">)</span> <span class="o">{</span>
<span class="c1">// TODO 后面实现</span>
<span class="o">}</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
<p>点击<code class="language-plaintext highlighter-rouge">Sync Gradle</code> 后,gradle task 会增加两个任务 ` testDebugUnitTestCoverage<code class="language-plaintext highlighter-rouge">, </code>testReleaseUnitTestCoverage`,接下来我们增加实现报告生成的任务。</p>
</li>
<li>
<p>使用一下代码替换上一个步骤中的<code class="language-plaintext highlighter-rouge">TODO</code></p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">group</span> <span class="o">=</span> <span class="s2">"Reporting"</span>
<span class="n">description</span> <span class="o">=</span> <span class="s2">"Generate Jacoco coverage reports for the ${variantName.capitalize()} build."</span>
<span class="c1">// 设置报告格式</span>
<span class="n">reports</span> <span class="o">{</span>
<span class="n">html</span><span class="o">.</span><span class="na">enabled</span> <span class="o">=</span> <span class="kc">true</span>
<span class="n">xml</span><span class="o">.</span><span class="na">enabled</span> <span class="o">=</span> <span class="kc">true</span>
<span class="o">}</span>
<span class="c1">// 排除不需要统计的类</span>
<span class="kt">def</span> <span class="n">excludes</span> <span class="o">=</span> <span class="o">[</span>
<span class="s1">'**/R.class'</span><span class="o">,</span>
<span class="s1">'**/R$*.class'</span><span class="o">,</span>
<span class="s1">'**/BuildConfig.*'</span><span class="o">,</span>
<span class="s1">'**/Manifest*.*'</span><span class="o">,</span>
<span class="s1">'**/*Test*.*'</span><span class="o">,</span>
<span class="s1">'android/**/*.*'</span><span class="o">,</span>
<span class="s1">'androidx/**/*.*'</span>
<span class="o">]</span>
<span class="c1">// Java 类文件</span>
<span class="kt">def</span> <span class="n">javaClasses</span> <span class="o">=</span> <span class="n">fileTree</span><span class="o">(</span><span class="nl">dir:</span> <span class="n">variant</span><span class="o">.</span><span class="na">javaCompiler</span><span class="o">.</span><span class="na">destinationDir</span><span class="o">,</span> <span class="nl">excludes:</span> <span class="n">excludes</span><span class="o">)</span>
<span class="c1">// Kotlin 文件</span>
<span class="kt">def</span> <span class="n">kotlinClasses</span> <span class="o">=</span> <span class="n">fileTree</span><span class="o">(</span><span class="nl">dir:</span> <span class="s2">"${buildDir}/tmp/kotlin-classes/${variantName}"</span><span class="o">,</span> <span class="nl">excludes:</span> <span class="n">excludes</span><span class="o">)</span>
<span class="n">classDirectories</span> <span class="o">=</span> <span class="n">files</span><span class="o">([</span><span class="n">javaClasses</span><span class="o">,</span> <span class="n">kotlinClasses</span><span class="o">])</span>
<span class="c1">// 源文件</span>
<span class="n">sourceDirectories</span> <span class="o">=</span> <span class="n">files</span><span class="o">([</span>
<span class="s2">"$project.projectDir/src/main/java"</span><span class="o">,</span>
<span class="s2">"$project.projectDir/src/${variantName}/java"</span><span class="o">,</span>
<span class="s2">"$project.projectDir/src/main/kotlin"</span><span class="o">,</span>
<span class="s2">"$project.projectDir/src/${variantName}/kotlin"</span>
<span class="o">])</span>
<span class="c1">// 最开始我们生成的文件</span>
<span class="n">executionData</span> <span class="o">=</span> <span class="n">files</span><span class="o">(</span><span class="s2">"${project.buildDir}/jacoco/${testTaskName}.exec"</span><span class="o">)</span>
</code></pre></div> </div>
</li>
<li>
<p>执行一下 <code class="language-plaintext highlighter-rouge">testDebugUnitTestCoverage</code> 任务,我们就会在build 目录下看到报告了</p>
<p><img src="/assets/images/jacoco-reports.png" alt="" /></p>
<p>经过以上步骤我们完成了一个jacoco 报告的生成过程。</p>
</li>
</ol>
<p>关键步骤来了,如何在打包的app中开启jacoco呢?</p>
<ol>
<li>
<p>新建一个<code class="language-plaintext highlighter-rouge">staging</code> 的build type</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">buildTypes</span> <span class="o">{</span>
<span class="n">release</span> <span class="o">{...}</span>
<span class="n">staging</span> <span class="o">{</span>
<span class="n">initWith</span><span class="o">(</span><span class="n">debug</span><span class="o">)</span>
<span class="n">matchingFallbacks</span> <span class="o">=</span> <span class="o">[</span><span class="s2">"debug"</span><span class="o">]</span>
<span class="n">testCoverageEnabled</span> <span class="kc">true</span> <span class="c1">// 会将jacoco runtime打包至app中</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div> </div>
</li>
<li>
<p>在<code class="language-plaintext highlighter-rouge">src</code> 目录下,与 <code class="language-plaintext highlighter-rouge">main</code> 通级,新建 <code class="language-plaintext highlighter-rouge">staging</code> 目录</p>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">staging</code> 目录下新建 <code class="language-plaintext highlighter-rouge">java</code>目录,并在 <code class="language-plaintext highlighter-rouge">com.example.staging</code> 包下新建 <code class="language-plaintext highlighter-rouge">StagingApp.kt</code>文件,代码如下:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">package</span> <span class="nn">com.example.staging</span>
<span class="k">import</span> <span class="nn">android.Manifest</span>
<span class="k">import</span> <span class="nn">android.app.Activity</span>
<span class="k">import</span> <span class="nn">android.app.Application</span>
<span class="k">import</span> <span class="nn">android.os.Bundle</span>
<span class="k">import</span> <span class="nn">android.os.Environment</span>
<span class="k">import</span> <span class="nn">android.util.Log</span>
<span class="k">import</span> <span class="nn">android.widget.Toast</span>
<span class="k">import</span> <span class="nn">androidx.fragment.app.FragmentActivity</span>
<span class="k">import</span> <span class="nn">com.tbruyelle.rxpermissions2.RxPermissions</span>
<span class="k">import</span> <span class="nn">java.io.File</span>
<span class="k">import</span> <span class="nn">java.io.FileOutputStream</span>
<span class="k">import</span> <span class="nn">java.io.IOException</span>
<span class="kd">class</span> <span class="nc">StagingApp</span> <span class="p">:</span> <span class="nc">Application</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">()</span>
<span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="nc">TAG</span><span class="p">,</span> <span class="s">"StagingApp"</span><span class="p">)</span>
<span class="nf">registerActivityLifecycleCallbacks</span><span class="p">(</span><span class="kd">object</span> <span class="err">: </span><span class="nc">ActivityLifecycleCallbacks</span> <span class="p">{</span>
<span class="kd">var</span> <span class="py">activitySize</span> <span class="p">=</span> <span class="mi">0</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivityPaused</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">?)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivityResumed</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">?)</span> <span class="p">{</span>
<span class="c1">// 第一个activity 请求 SD card 目录访问权限</span>
<span class="k">if</span> <span class="p">(</span><span class="n">activitySize</span> <span class="p">==</span> <span class="mi">1</span><span class="p">)</span> <span class="p">{</span>
<span class="p">(</span><span class="n">activity</span> <span class="k">as</span><span class="p">?</span> <span class="nc">FragmentActivity</span><span class="p">)</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">rxPerm</span> <span class="p">=</span> <span class="nc">RxPermissions</span><span class="p">(</span><span class="n">it</span><span class="p">)</span>
<span class="n">rxPerm</span><span class="p">.</span><span class="nf">request</span><span class="p">(</span><span class="nc">Manifest</span><span class="p">.</span><span class="n">permission</span><span class="p">.</span><span class="nc">WRITE_EXTERNAL_STORAGE</span><span class="p">).</span><span class="nf">subscribe</span><span class="p">({</span> <span class="n">result</span> <span class="p">-></span>
<span class="k">if</span> <span class="p">(!</span><span class="n">result</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">Toast</span><span class="p">.</span><span class="nf">makeText</span><span class="p">(</span>
<span class="n">it</span><span class="p">,</span>
<span class="s">"You have to grant the permission to save coverage file"</span><span class="p">,</span>
<span class="nc">Toast</span><span class="p">.</span><span class="nc">LENGTH_SHORT</span>
<span class="p">).</span><span class="nf">show</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">},</span> <span class="p">{</span> <span class="n">e</span> <span class="p">-></span>
<span class="n">e</span><span class="p">.</span><span class="nf">printStackTrace</span><span class="p">()</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivityStarted</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">?)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivityDestroyed</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">?)</span> <span class="p">{</span>
<span class="n">activitySize</span> <span class="p">-=</span> <span class="mi">1</span>
<span class="k">if</span> <span class="p">(</span><span class="n">activitySize</span> <span class="p"><=</span> <span class="mi">0</span><span class="p">)</span> <span class="p">{</span>
<span class="c1">//所有activity被销毁后,生产报告文件</span>
<span class="nf">generateCoverageReport</span><span class="p">(</span><span class="nf">createFile</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivitySaveInstanceState</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">?,</span> <span class="n">outState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivityStopped</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">?)</span> <span class="p">{</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivityCreated</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">Activity</span><span class="p">?,</span> <span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
<span class="n">activitySize</span> <span class="p">+=</span> <span class="mi">1</span>
<span class="p">}</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="k">private</span> <span class="k">fun</span> <span class="nf">generateCoverageReport</span><span class="p">(</span><span class="n">file</span><span class="p">:</span> <span class="nc">File</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="nc">TAG</span><span class="p">,</span> <span class="s">"generateCoverageReport():${file.absolutePath}"</span><span class="p">)</span>
<span class="nc">FileOutputStream</span><span class="p">(</span><span class="n">file</span><span class="p">,</span> <span class="k">false</span><span class="p">).</span><span class="nf">use</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">agent</span> <span class="p">=</span> <span class="nc">Class</span><span class="p">.</span><span class="nf">forName</span><span class="p">(</span><span class="s">"org.jacoco.agent.rt.RT"</span><span class="p">)</span>
<span class="p">.</span><span class="nf">getMethod</span><span class="p">(</span><span class="s">"getAgent"</span><span class="p">)</span>
<span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="k">null</span><span class="p">)</span>
<span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="nc">TAG</span><span class="p">,</span> <span class="n">agent</span><span class="p">.</span><span class="nf">toString</span><span class="p">())</span>
<span class="n">it</span><span class="p">.</span><span class="nf">write</span><span class="p">(</span>
<span class="n">agent</span><span class="p">.</span><span class="n">javaClass</span><span class="p">.</span><span class="nf">getMethod</span><span class="p">(</span><span class="s">"getExecutionData"</span><span class="p">,</span> <span class="nc">Boolean</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">javaPrimitiveType</span><span class="p">)</span>
<span class="p">.</span><span class="nf">invoke</span><span class="p">(</span><span class="n">agent</span><span class="p">,</span> <span class="k">false</span><span class="p">)</span> <span class="k">as</span> <span class="nc">ByteArray</span>
<span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">fun</span> <span class="nf">createFile</span><span class="p">():</span> <span class="nc">File</span> <span class="p">{</span>
<span class="c1">// SD card 下面</span>
<span class="kd">val</span> <span class="py">file</span> <span class="p">=</span> <span class="nc">File</span><span class="p">(</span><span class="nc">Environment</span><span class="p">.</span><span class="nf">getExternalStorageDirectory</span><span class="p">(),</span> <span class="s">"jacoco/$DEFAULT_COVERAGE_FILE_PATH"</span><span class="p">)</span>
<span class="k">if</span> <span class="p">(!</span><span class="n">file</span><span class="p">.</span><span class="nf">exists</span><span class="p">())</span> <span class="p">{</span>
<span class="k">try</span> <span class="p">{</span>
<span class="n">file</span><span class="p">.</span><span class="n">parentFile</span><span class="o">?.</span><span class="nf">mkdirs</span><span class="p">()</span>
<span class="n">file</span><span class="p">.</span><span class="nf">createNewFile</span><span class="p">()</span>
<span class="p">}</span> <span class="k">catch</span> <span class="p">(</span><span class="n">e</span><span class="p">:</span> <span class="nc">IOException</span><span class="p">)</span> <span class="p">{</span>
<span class="nc">Log</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="nc">TAG</span><span class="p">,</span> <span class="s">"异常 : $e"</span><span class="p">)</span>
<span class="n">e</span><span class="p">.</span><span class="nf">printStackTrace</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">return</span> <span class="n">file</span>
<span class="p">}</span>
<span class="k">companion</span> <span class="k">object</span> <span class="p">{</span>
<span class="k">const</span> <span class="kd">val</span> <span class="py">DEFAULT_COVERAGE_FILE_PATH</span> <span class="p">=</span> <span class="s">"jacoco-coverage.ec"</span>
<span class="k">const</span> <span class="kd">val</span> <span class="py">TAG</span> <span class="p">=</span> <span class="s">"StagingApp"</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">staging</code> 目录中新建一个 <code class="language-plaintext highlighter-rouge">AndroidManifest.xml</code> 文件,内容如下</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp"><?xml version="1.0" encoding="utf-8"?></span>
<span class="nt"><manifest</span>
<span class="na">xmlns:android=</span><span class="s">"http://schemas.android.com/apk/res/android"</span> <span class="na">package=</span><span class="s">"com.example.jacocomanual"</span><span class="nt">></span>
<span class="nt"><uses-permission</span> <span class="na">android:name=</span><span class="s">"android.permission.WRITE_EXTERNAL_STORAGE"</span><span class="nt">/></span>
<span class="nt"><application</span> <span class="na">android:name=</span><span class="s">"com.example.staging.StagingApp"</span><span class="nt">/></span>
<span class="nt"></manifest></span>
</code></pre></div> </div>
<p>最后效果如下:</p>
<p><img src="/assets/images/jacoco-staging-build-type.png" alt="jacoco-staging-build-type" /></p>
</li>
<li>
<p>IDE <code class="language-plaintext highlighter-rouge">Build Variants</code> 下选择 <code class="language-plaintext highlighter-rouge">staging</code></p>
<p><img src="/assets/images/jacoco-build-variants.png" alt="jacoco-build-variants" /></p>
</li>
<li>
<p>运行app并安装到设备或者模拟器,操作一下,然后按返回键关闭所有的页面,这时候会在 SD 卡目录下的生成 <code class="language-plaintext highlighter-rouge">jacoco/jacoco-coverage.ec</code> 文件。</p>
<p><img src="/assets/images/jacoco-sd-location.png" alt="jacoco-sd-location" /></p>
</li>
<li>
<p>复制 <code class="language-plaintext highlighter-rouge">jacoco-coverage.ec</code> 文件到项目根目录下的 <code class="language-plaintext highlighter-rouge">jacoco</code> 文件夹</p>
</li>
<li>
<p>我们来修改jacoco的任务来生成最后的报告</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="c1">// 最开始我们生成的文件</span>
<span class="n">executionData</span> <span class="o">=</span> <span class="n">files</span><span class="o">([</span>
<span class="s2">"${project.buildDir}/jacoco/${testTaskName}.exec"</span><span class="o">,</span>
<span class="s2">"${rootDir}/jacoco/jacoco-coverage.ec"</span> <span class="c1">//增加一个数据源</span>
<span class="o">])</span>
</code></pre></div> </div>
</li>
<li>
<p>运行 <code class="language-plaintext highlighter-rouge">testStagingUnitTest</code> 这样就可以看到报告了</p>
<p><img src="/assets/images/jacoco-report-html.png" alt="jacoco-report-html" /></p>
</li>
</ol>
<p>真香!~~</p>Trevor Wang 团队中目前还没有自动化测试的覆盖,所以测试 team 想了解下手动测试的覆盖率。于是才有了本片文章的产生。网上有很多文章是利用 Android 的 instrument 测试框架,然后通过命令来启动app来进行测试。而且报告生产的时间点是在启动的 activity 结束以后,在复杂场景下,是没有办法来捕捉到所有页面的函数调用的。flutter_boost 集成体验之Android2019-04-10T00:00:00+00:002019-04-10T00:00:00+00:00https://trevorwang.github.io/2019/04/10/flutter-boost<blockquote>
<p>如何将 flutter_boost 添加到已有的Android工程,此 demo 在 MacOS 下完成,如果使用 Windows, 请修改对应的路径为 Windows 的格式。</p>
</blockquote>
<h2 id="flutter-端">flutter 端</h2>
<ol>
<li>
<p>新建 flutter module</p>
<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code> flutter create <span class="nt">-t</span> module flutter_module
</code></pre></div> </div>
</li>
<li>
<p>在 <code class="language-plaintext highlighter-rouge">pubspec.yaml</code> 中添加 flutter_boost 依赖</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="na">dependencies</span><span class="pi">:</span>
<span class="err"> </span> <span class="na">flutter_boost</span><span class="pi">:</span>
</code></pre></div> </div>
</li>
<li>
<p>运行 <code class="language-plaintext highlighter-rouge">flutter build apk</code>
在 flutter_boost 0.400版本会有错误抛出,找出其中关键信息如下:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> * What went wrong:
A problem occurred evaluating project ':flutter_boost'.
> Plugin with id 'kotlin-android-extensions' not found.
</code></pre></div> </div>
<p>不难发现是因为没有 <code class="language-plaintext highlighter-rouge">kotlin</code> 依赖导致, 下面我们就来修复这个问题</p>
</li>
<li>在项目根目录下执行,<code class="language-plaintext highlighter-rouge">flutter make-host-app-editable</code>
执行完成后,发现目录下面出现了 <code class="language-plaintext highlighter-rouge">android</code> 以及 <code class="language-plaintext highlighter-rouge">ios</code> 文件夹</li>
<li>
<p>在 <code class="language-plaintext highlighter-rouge">android/build.gralde</code> 中添加 <code class="language-plaintext highlighter-rouge">kotlin</code> 依赖</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> buildscript {
+ ext.kotlin_version = '1.3.21'
repositories {
google()
jcenter()
@@ -8,6 +9,7 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
</code></pre></div> </div>
<p>再一次运行 <code class="language-plaintext highlighter-rouge">flutter build apk</code> 发现成功了</p>
</li>
<li>把 <code class="language-plaintext highlighter-rouge">flutter_boost/example/lib</code> 下的文件复制到 <code class="language-plaintext highlighter-rouge">lib</code> 目录</li>
</ol>
<p>这样 flutter 这一端的工作就准备完成了</p>
<h2 id="android-端">Android 端</h2>
<ol>
<li>
<p>使用Android Studio新建一个工程</p>
<p>如果你有现成的项目,可以跳过这一步</p>
</li>
<li>
<p>在 App module 中添加kotlin支持,并在 <code class="language-plaintext highlighter-rouge">settings.gradle</code> 中添加如下代码</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">setBinding</span><span class="o">(</span><span class="k">new</span> <span class="n">Binding</span><span class="o">([</span><span class="nl">gradle:</span> <span class="k">this</span><span class="o">]))</span>
<span class="n">evaluate</span><span class="o">(</span><span class="k">new</span> <span class="n">File</span><span class="o">(</span><span class="s1">'../flutter_module/.android/include_flutter.groovy'</span><span class="o">))</span> <span class="c1">//保证Android项目和flutter module在同一目录下</span>
</code></pre></div> </div>
<p>运行一下 <code class="language-plaintext highlighter-rouge">gralde sync</code>,这时候你的工程里应该会出现三个新的项目 <code class="language-plaintext highlighter-rouge">Flutter-flutter</code>, <code class="language-plaintext highlighter-rouge">flutter_boost</code> 和 <code class="language-plaintext highlighter-rouge">xservice_kit</code></p>
<p>如果碰到一下错误,请把gradle的版本改为 <strong>4.10.2</strong></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>ERROR: Unable to resolve dependency for ':flutter@dynamicProfile/compileClasspath': Could not resolve project :xservice_kit.
Show Details
Affected Modules: flutter
ERROR: Unable to resolve dependency for ':flutter@dynamicProfile/compileClasspath': Could not resolve project :flutter_boost.
Show Details
Affected Modules: flutter
</code></pre></div> </div>
</li>
<li>
<p>在 <code class="language-plaintext highlighter-rouge">flutterservice</code> 目录下的 <code class="language-plaintext highlighter-rouge">build.gradle</code> 文件中添加一下依赖</p>
<div class="language-groovy highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">implementation</span> <span class="nf">project</span><span class="o">(</span><span class="nl">path:</span> <span class="s1">':flutter'</span><span class="o">)</span>
<span class="n">implementation</span> <span class="nf">project</span><span class="o">(</span><span class="s2">":flutter_boost"</span><span class="o">)</span>
</code></pre></div> </div>
</li>
<li>
<p>从<code class="language-plaintext highlighter-rouge">flutter_boost/example/android</code> 中复制所有chu新建 <code class="language-plaintext highlighter-rouge">MyApplication</code> 类,代码如下:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">class</span> <span class="nc">MyApplication</span> <span class="p">:</span> <span class="nc">FlutterApplication</span><span class="p">(),</span> <span class="nc">IPlatform</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">()</span>
<span class="nc">FlutterBoostPlugin</span><span class="p">.</span><span class="nf">init</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">getApplication</span><span class="p">():</span> <span class="nc">Application</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">this</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">getMainActivity</span><span class="p">():</span> <span class="nc">Activity</span><span class="p">?</span> <span class="p">{</span>
<span class="k">return</span> <span class="nc">MainActivity</span><span class="p">.</span><span class="n">sRef</span><span class="p">.</span><span class="k">get</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">isDebug</span><span class="p">():</span> <span class="nc">Boolean</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">true</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">startActivity</span><span class="p">(</span><span class="n">context</span><span class="p">:</span> <span class="nc">Context</span><span class="p">?,</span> <span class="n">url</span><span class="p">:</span> <span class="nc">String</span><span class="p">?,</span> <span class="n">requestCode</span><span class="p">:</span> <span class="nc">Int</span><span class="p">):</span> <span class="nc">Boolean</span> <span class="p">{</span>
<span class="k">return</span> <span class="k">false</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">getSettings</span><span class="p">():</span> <span class="nc">MutableMap</span><span class="p"><</span><span class="nc">Any</span><span class="err">?</span><span class="p">,</span> <span class="nc">Any</span><span class="err">?</span><span class="p">></span> <span class="p">{</span>
<span class="k">return</span> <span class="nc">HashMap</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div> </div>
<p>完成后再manifest文件中指定使用此Application</p>
</li>
<li>
<p>在 MainActivity 中添加弱引用来保存 <code class="language-plaintext highlighter-rouge">MainActivity</code></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
<span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span>
<span class="n">sRef</span> <span class="p">=</span> <span class="nc">WeakReference</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">companion</span> <span class="k">object</span> <span class="p">{</span>
<span class="kd">var</span> <span class="py">sRef</span><span class="p">:</span> <span class="nc">WeakReference</span><span class="p"><</span><span class="nc">Activity</span><span class="err">?</span><span class="p">></span> <span class="p">=</span> <span class="nc">WeakReference</span><span class="p">(</span><span class="k">null</span><span class="p">)</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div> </div>
</li>
<li>
<p>新建FlutterPageActivity, 记得添加到manifest文件中</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">class</span> <span class="nc">FlutterPageActivity</span><span class="p">:</span><span class="nc">BoostFlutterActivity</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">getContainerParams</span><span class="p">():</span> <span class="nc">MutableMap</span><span class="p"><</span><span class="nc">Any</span><span class="err">?</span><span class="p">,</span> <span class="nc">Any</span><span class="err">?</span><span class="p">></span> <span class="p">{</span>
<span class="k">return</span> <span class="nc">HashMap</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onRegisterPlugins</span><span class="p">(</span><span class="n">registry</span><span class="p">:</span> <span class="nc">PluginRegistry</span><span class="p">?)</span> <span class="p">{</span>
<span class="nc">GeneratedPluginRegistrant</span><span class="p">.</span><span class="nf">registerWith</span><span class="p">(</span><span class="n">registry</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">getContainerName</span><span class="p">():</span> <span class="nc">String</span> <span class="p">{</span>
<span class="k">return</span> <span class="s">"flutterPage"</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div> </div>
</li>
<li>
<p>在layout中给textview 添加id</p>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> android:text="Hello World!"
android:id="@+id/tvHello"
app:layout_constraintBottom_toBottomOf="parent"
</code></pre></div> </div>
</li>
<li>
<p>MainActivity 中添加点击事件</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">tvHello</span><span class="p">.</span><span class="nf">setOnClickListener</span> <span class="p">{</span>
<span class="nf">startActivity</span><span class="p">(</span><span class="nc">Intent</span><span class="p">(</span><span class="n">applicationContext</span><span class="p">,</span> <span class="nc">FlutterPageActivity</span><span class="o">::</span><span class="k">class</span><span class="p">.</span><span class="n">java</span><span class="p">))</span>
<span class="p">}</span>
</code></pre></div> </div>
</li>
<li>运行android工程,如果碰到错误请按照提示修改</li>
<li>
<p>点击 <code class="language-plaintext highlighter-rouge">Hello world</code>,就会打开一个Flutter的页面了,到此已经完成了从Native页面打开flutter页面</p>
</li>
<li>把MyApplication 中 startActivity 函数修改为下面代码:</li>
</ol>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> override fun startActivity(context: Context?, url: String?, requestCode: Int): Boolean {
if (url?.contains("sample://nativePage") == true) {
context?.startActivity(Intent(context, MainActivity::class.java))
return true
}
return false
}
</code></pre></div></div>
<p>点击flutter页面中的<code class="language-plaintext highlighter-rouge">open native page</code>,就会重新打开activity。</p>
<p>这样就完成了两端的相互调用,欢迎入坑 Flutter</p>Trevor Wang如何将 flutter_boost 添加到已有的Android工程,此 demo 在 MacOS 下完成,如果使用 Windows, 请修改对应的路径为 Windows 的格式。Flutter 插件开发 – Android篇2018-06-16T00:00:00+00:002018-06-16T00:00:00+00:00https://trevorwang.github.io/2018/06/16/flutter-plugin-development-for-android<p>目前 flutter 的功能还么有很完善,很多常用的三方库在 flutter 中没有类似的实现。如何在 flutter 中使用已经存在的 native 库呢?本文以二维码扫描为例,介绍 flutter 插件开发。</p>
<h2 id="新建-plugin-工程">新建 Plugin 工程</h2>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter create <span class="nt">--org</span> mingsin.scan <span class="nt">--template</span><span class="o">=</span>plugin <span class="nt">-i</span> swift <span class="nt">-a</span> kotlin scan
</code></pre></div></div>
<p>打开新创建的工程,可以看到一下代码:</p>
<p><code class="language-plaintext highlighter-rouge">lib/scan.dart</code></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'dart:async'</span><span class="o">;</span>
<span class="kn">import</span> <span class="s">'package:flutter/services.dart'</span><span class="o">;</span>
<span class="kd">class</span> <span class="nc">Scan</span> <span class="o">{</span>
<span class="kd">static</span> <span class="kd">const</span> <span class="n">MethodChannel</span> <span class="n">_channel</span> <span class="o">=</span>
<span class="kd">const</span> <span class="n">MethodChannel</span><span class="o">(</span><span class="s">'scan'</span><span class="o">);</span>
<span class="kd">static</span> <span class="n">Future</span><span class="o"><</span><span class="kt">String</span><span class="o">></span> <span class="kd">get</span> <span class="n">platformVersion</span> <span class="n">async</span> <span class="o">{</span>
<span class="kd">final</span> <span class="kt">String</span> <span class="n">version</span> <span class="o">=</span> <span class="n">await</span> <span class="n">_channel</span><span class="o">.</span><span class="na">invokeMethod</span><span class="o">(</span><span class="s">'getPlatformVersion'</span><span class="o">);</span>
<span class="k">return</span> <span class="n">version</span><span class="o">;</span>
<span class="o">}</span>
<span class="o">}</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">android/src/..../ScanPlugin.kt</code></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ScanPlugin</span><span class="p">():</span> <span class="nc">MethodCallHandler</span> <span class="p">{</span>
<span class="k">companion</span> <span class="k">object</span> <span class="p">{</span>
<span class="nd">@JvmStatic</span>
<span class="k">fun</span> <span class="nf">registerWith</span><span class="p">(</span><span class="n">registrar</span><span class="p">:</span> <span class="nc">Registrar</span><span class="p">):</span> <span class="nc">Unit</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">channel</span> <span class="p">=</span> <span class="nc">MethodChannel</span><span class="p">(</span><span class="n">registrar</span><span class="p">.</span><span class="nf">messenger</span><span class="p">(),</span> <span class="s">"scan"</span><span class="p">)</span>
<span class="n">channel</span><span class="p">.</span><span class="nf">setMethodCallHandler</span><span class="p">(</span><span class="nc">ScanPlugin</span><span class="p">())</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onMethodCall</span><span class="p">(</span><span class="n">call</span><span class="p">:</span> <span class="nc">MethodCall</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">Result</span><span class="p">):</span> <span class="nc">Unit</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">call</span><span class="p">.</span><span class="n">method</span><span class="p">.</span><span class="nf">equals</span><span class="p">(</span><span class="s">"getPlatformVersion"</span><span class="p">))</span> <span class="p">{</span>
<span class="n">result</span><span class="p">.</span><span class="nf">success</span><span class="p">(</span><span class="s">"Android ${android.os.Build.VERSION.RELEASE}"</span><span class="p">)</span>
<span class="p">}</span> <span class="k">else</span> <span class="p">{</span>
<span class="n">result</span><span class="p">.</span><span class="nf">notImplemented</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>通过以上代码,可以清晰看出来,Flutter 通过提供 <code class="language-plaintext highlighter-rouge">MethodChannel</code> 来实现跨语言的调用。
Kotlin 的代码中,有个叫做 <code class="language-plaintext highlighter-rouge">ScanPlugin</code> 的类,这个类继承自 <code class="language-plaintext highlighter-rouge">MethodCallHandler</code>。它会先向 Flutter 注册一个叫做 scan 的 channel, 然后在 <code class="language-plaintext highlighter-rouge">onMethodCall</code> 判断当函数名为 <code class="language-plaintext highlighter-rouge">getPlatformVersion</code> 时,返回 android 系统的 OS 版本。</p>
<p>在 Dart 中,获取一个注册明为 <code class="language-plaintext highlighter-rouge">scan</code> 的 <code class="language-plaintext highlighter-rouge">MethodChannel</code>。然后通过 <code class="language-plaintext highlighter-rouge">_channel.invokeMethod('getPlatformVersion');</code> 来获取对应的函数的结果。</p>
<p>通过以上分析,我看就可以看得出来,如果要添加一个叫做 <code class="language-plaintext highlighter-rouge">codescan</code> 的函数就可以了。</p>
<h2 id="添加-codescan-函数">添加 <code class="language-plaintext highlighter-rouge">codescan</code> 函数</h2>
<h3 id="先执行以下命令">先执行以下命令</h3>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>```shell
cd example;
flutter build apk
``` ### 导入 Android Plugin 工程
</code></pre></div></div>
<ol>
<li>打开 Android Studio</li>
<li>选择 Import project,然后选中 <code class="language-plaintext highlighter-rouge">example/android/build.gradle</code> 文件</li>
<li>在 Gradle Sync 窗口中,选择 OK</li>
<li>Android Gradle Plugin Update 窗口中选择 Don’t remind me again for this project</li>
</ol>
<p>这样你就可以运行 example 工程了</p>
<h3 id="libscandart-中添加函数"><code class="language-plaintext highlighter-rouge">lib/scan.dart</code> 中添加函数</h3>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">static</span> <span class="n">Future</span> <span class="nf">codeScan</span><span class="p">(</span><span class="o">)</span> <span class="n">async</span> <span class="o">{</span>
<span class="n">await</span> <span class="n">_channel</span><span class="o">.</span><span class="na">invokeMethod</span><span class="o">(</span><span class="s">'codeScan'</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<h3 id="scanpluginkt-中实现该函数"><code class="language-plaintext highlighter-rouge">ScanPlugin.kt</code> 中实现该函数</h3>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">ScanPlugin</span><span class="p">(</span><span class="k">private</span> <span class="kd">val</span> <span class="py">registrar</span><span class="p">:</span> <span class="nc">Registrar</span><span class="p">)</span> <span class="p">:</span> <span class="nc">MethodCallHandler</span> <span class="p">{</span>
<span class="k">companion</span> <span class="k">object</span> <span class="p">{</span>
<span class="nd">@JvmStatic</span>
<span class="k">fun</span> <span class="nf">registerWith</span><span class="p">(</span><span class="n">registrar</span><span class="p">:</span> <span class="nc">Registrar</span><span class="p">)</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">channel</span> <span class="p">=</span> <span class="nc">MethodChannel</span><span class="p">(</span><span class="n">registrar</span><span class="p">.</span><span class="nf">messenger</span><span class="p">(),</span> <span class="s">"scan"</span><span class="p">)</span>
<span class="n">channel</span><span class="p">.</span><span class="nf">setMethodCallHandler</span><span class="p">(</span><span class="nc">ScanPlugin</span><span class="p">(</span><span class="n">registrar</span><span class="p">))</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onMethodCall</span><span class="p">(</span><span class="n">call</span><span class="p">:</span> <span class="nc">MethodCall</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">Result</span><span class="p">)</span> <span class="p">{</span>
<span class="k">when</span> <span class="p">(</span><span class="n">call</span><span class="p">.</span><span class="n">method</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"getPlatformVersion"</span> <span class="p">-></span> <span class="p">{</span>
<span class="n">result</span><span class="p">.</span><span class="nf">success</span><span class="p">(</span><span class="s">"Android ${android.os.Build.VERSION.RELEASE}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="s">"codeScan"</span> <span class="p">-></span> <span class="p">{</span>
<span class="nc">IntentIntegrator</span><span class="p">(</span><span class="n">registrar</span><span class="p">.</span><span class="nf">activity</span><span class="p">())</span>
<span class="p">.</span><span class="nf">setDesiredBarcodeFormats</span><span class="p">(</span><span class="nc">IntentIntegrator</span><span class="p">.</span><span class="nc">ONE_D_CODE_TYPES</span><span class="p">)</span>
<span class="p">.</span><span class="nf">initiateScan</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">-></span>
<span class="n">result</span><span class="p">.</span><span class="nf">notImplemented</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="examplelibmaindart-中调用该函数"><code class="language-plaintext highlighter-rouge">example/lib/main.dart</code> 中调用该函数</h3>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> body: new Center(
<span class="gd">- child: new Text('Running on: $_platformVersion\n'),
</span><span class="gi">+ child: RaisedButton(
+ onPressed: () async {
+ await Scan.codeScan();
+ },
+ child: Text("Scan"),
+ ),
</span> ),
),
</code></pre></div></div>
<p>重新 reload 该工程,应该可以看到如下界面:
<img src="/assets/images/flutter-scan-button.png" alt="flutter code scan" />
点击 Scan 按钮,就可以看到二维码扫描的界面了。</p>
<p><img src="/assets/images/flutter-scan-camera.png" alt="flutter scan camera" /></p>
<p>到这里你就可以实现 dart 来调用三方库来实现具体的功能了。</p>
<p>如何获取第三方库的返回值呢?MethodHandler提供了一个 result 类传递返回值,只需要在 success 函数中传递你需要的返回值就可以了。同时如果需要获取 onActivity 的返回值,可以通过 PluginRegistry.ActivityResultListener 来实现。</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">override</span> <span class="k">fun</span> <span class="nf">onMethodCall</span><span class="p">(</span><span class="n">call</span><span class="p">:</span> <span class="nc">MethodCall</span><span class="p">,</span> <span class="n">result</span><span class="p">:</span> <span class="nc">Result</span><span class="p">)</span> <span class="p">{</span>
<span class="k">when</span> <span class="p">(</span><span class="n">call</span><span class="p">.</span><span class="n">method</span><span class="p">)</span> <span class="p">{</span>
<span class="s">"getPlatformVersion"</span> <span class="p">-></span> <span class="p">{</span>
<span class="n">result</span><span class="p">.</span><span class="nf">success</span><span class="p">(</span><span class="s">"Android ${android.os.Build.VERSION.RELEASE}"</span><span class="p">)</span>
<span class="p">}</span>
<span class="s">"codeScan"</span> <span class="p">-></span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span><span class="n">listener</span><span class="p">.</span><span class="n">result</span> <span class="p">==</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="n">registrar</span><span class="p">.</span><span class="nf">addActivityResultListener</span><span class="p">(</span><span class="n">listener</span><span class="p">)</span>
<span class="p">}</span>
<span class="n">listener</span><span class="p">.</span><span class="n">result</span> <span class="p">=</span> <span class="n">result</span>
<span class="nc">IntentIntegrator</span><span class="p">(</span><span class="n">registrar</span><span class="p">.</span><span class="nf">activity</span><span class="p">())</span>
<span class="p">.</span><span class="nf">setDesiredBarcodeFormats</span><span class="p">(</span><span class="nc">IntentIntegrator</span><span class="p">.</span><span class="nc">ONE_D_CODE_TYPES</span><span class="p">)</span>
<span class="p">.</span><span class="nf">initiateScan</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">-></span>
<span class="n">result</span><span class="p">.</span><span class="nf">notImplemented</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">listener</span> <span class="p">=</span> <span class="kd">object</span> <span class="err">: </span><span class="nc">PluginRegistry</span><span class="p">.</span><span class="nc">ActivityResultListener</span> <span class="p">{</span>
<span class="kd">var</span> <span class="py">result</span><span class="p">:</span> <span class="nc">Result</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onActivityResult</span><span class="p">(</span><span class="n">requestCode</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">resultCode</span><span class="p">:</span> <span class="nc">Int</span><span class="p">,</span> <span class="n">intent</span><span class="p">:</span> <span class="nc">Intent</span><span class="p">?):</span> <span class="nc">Boolean</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">intentResult</span> <span class="p">=</span> <span class="nc">IntentIntegrator</span><span class="p">.</span><span class="nf">parseActivityResult</span><span class="p">(</span><span class="n">requestCode</span><span class="p">,</span> <span class="n">intent</span><span class="p">)</span>
<span class="n">result</span><span class="o">?.</span><span class="nf">success</span><span class="p">(</span><span class="n">intentResult</span><span class="p">.</span><span class="n">contents</span><span class="p">)</span>
<span class="k">return</span> <span class="k">true</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>Trevor Wang目前 flutter 的功能还么有很完善,很多常用的三方库在 flutter 中没有类似的实现。如何在 flutter 中使用已经存在的 native 库呢?本文以二维码扫描为例,介绍 flutter 插件开发。 新建 Plugin 工程 flutter create --org mingsin.scan --template=plugin -i swift -a kotlin scan 打开新创建的工程,可以看到一下代码:Flutter widgets framework 介绍2018-06-11T00:00:00+00:002018-06-11T00:00:00+00:00https://trevorwang.github.io/2018/06/11/flutter-widgets-framework<h2 id="介绍">介绍</h2>
<p>Flutter widgets 是从 <a href="http://facebook.github.io/react/">React</a> 得到启发,从而产生的一套现在流行的响应式框架。其主要思想就是所有的 UI 使用 widgets 来创建。所有的 widgets 会给出当前的配置和状态。当一个 widget 有变化时,系统会计算出来所有的这个 widget背后的渲染树从前一个状态到当前状态的最小变化,从而重新构建自己。所以在 Flutter 中所有的 UI 都是通过各种 widgets 的组合来实现的。<strong><a href="https://flutter.io/widgets/">查看所有的 widgets</a></strong></p>
<h2 id="来一个-hello-world">来一个 Hello World</h2>
<p>将如下代码 copy 到你新建项目中的 <code class="language-plaintext highlighter-rouge">lib/main.dart</code>,运行一下将会看到一下效果。<strong>(如果你还没有新建过项目,请按照<a href="/2018/06/03/how-to-setup-development-environment.html">这里的步骤</a>新建一个项目)</strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="n">runApp</span><span class="o">(</span>
<span class="k">new</span> <span class="n">Text</span><span class="o">(</span>
<span class="s">'Hello, world!'</span><span class="o">,</span>
<span class="nl">textDirection:</span> <span class="n">TextDirection</span><span class="o">.</span><span class="na">ltr</span><span class="o">,</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/assets/images/flutter-hello-world-1.png" alt="Hello world" />
我们在 main 函数中,把一个 text 的 widget 传给了 <code class="language-plaintext highlighter-rouge">runApp</code> 这个函数,从而得到上面的效果。
如果我们需要 <code class="language-plaintext highlighter-rouge">Hello, world!</code> 居中显示怎么办,前面我讲了在 flutter 中我们需要各种 widgets 的组合。这里我们就需要一个叫做 <code class="language-plaintext highlighter-rouge">Center</code> 的widget 所以放在里面的组件,都会显示在这个组建的中间。我们改一下代码:</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="n">runApp</span><span class="o">(</span>
<span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span>
<span class="s">'Hello, world!'</span><span class="o">,</span>
<span class="nl">textDirection:</span> <span class="n">TextDirection</span><span class="o">.</span><span class="na">ltr</span><span class="o">,</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p>效果如下:
<img src="/assets/images/flutter-hello-world-2.png" alt="Hell world 2" />
<em>细心的朋友,可能注意到了,本段中 <code class="language-plaintext highlighter-rouge">Center</code> 跟 <code class="language-plaintext highlighter-rouge">Text</code> 的 <code class="language-plaintext highlighter-rouge">new</code> 关键字不见了, 这里是因为 Dart 2 提供的语法糖,让我们省略关键字 <code class="language-plaintext highlighter-rouge">new</code>。</em></p>
<p>我们把 <code class="language-plaintext highlighter-rouge">Text</code> 放到了 <code class="language-plaintext highlighter-rouge">Center</code> widget 的 <code class="language-plaintext highlighter-rouge">child</code> 属性中,然后这个文字就可以居中了,不要太简单。到这我们就碰到了两种类型的 widgets,一个是用来展示内容的 <code class="language-plaintext highlighter-rouge">Text</code>,一个是用来布局的 <code class="language-plaintext highlighter-rouge">Center</code>。 在 Flutter 的开发中,我们会大量使用这种 widgets 的组合来实现我们需要的效果。接下来我们需要一个白色的背景以及蓝色的字体。<strong><a href="https://flutter.io/widgets/layout/">更多布局类型的 widgets</a></strong></p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="n">runApp</span><span class="o">(</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span> <span class="c1">// background color</span>
<span class="nl">child:</span> <span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span>
<span class="s">'Hello, world!'</span><span class="o">,</span>
<span class="nl">textDirection:</span> <span class="n">TextDirection</span><span class="o">.</span><span class="na">ltr</span><span class="o">,</span>
<span class="nl">style:</span> <span class="n">TextStyle</span><span class="o">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="o">),</span> <span class="c1">// text color</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/assets/images/flutter-hello-world-3.png" alt="Hello world 3" />
为了实现白色背景,我们添加了一个 <code class="language-plaintext highlighter-rouge">Container</code> widget,然后颜色设置为 白色。我们看到 Text 没有 color 的属性,但是有个 style,所以我们通过构建一个白色的 TextStyle 来实现字体变成红色。TextStyle 中还可以改变字体的其他属性,自己可以在研究一下。</p>
<p>如何在文字下面添加一个按钮呢?</p>
<div class="language-dart highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">import</span> <span class="s">'package:flutter/material.dart'</span><span class="o">;</span>
<span class="kt">void</span> <span class="nf">main</span><span class="p">(</span><span class="o">)</span> <span class="o">{</span>
<span class="n">runApp</span><span class="o">(</span>
<span class="n">Container</span><span class="o">(</span>
<span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">white</span><span class="o">,</span>
<span class="nl">child:</span> <span class="n">Center</span><span class="o">(</span>
<span class="nl">child:</span> <span class="n">Column</span><span class="o">(</span> <span class="c1">// 实现上下布局</span>
<span class="nl">mainAxisAlignment:</span> <span class="n">MainAxisAlignment</span><span class="o">.</span><span class="na">center</span><span class="o">,</span>
<span class="nl">mainAxisSize:</span> <span class="n">MainAxisSize</span><span class="o">.</span><span class="na">min</span><span class="o">,</span>
<span class="nl">children:</span> <span class="o"><</span><span class="n">Widget</span><span class="o">>[</span>
<span class="n">Text</span><span class="o">(</span>
<span class="s">'Hello, world!'</span><span class="o">,</span>
<span class="nl">textDirection:</span> <span class="n">TextDirection</span><span class="o">.</span><span class="na">ltr</span><span class="o">,</span>
<span class="nl">style:</span> <span class="n">TextStyle</span><span class="o">(</span><span class="nl">color:</span> <span class="n">Colors</span><span class="o">.</span><span class="na">red</span><span class="o">),</span>
<span class="o">),</span>
<span class="n">RaisedButton</span><span class="o">(</span> <span class="c1">// 按钮</span>
<span class="nl">child:</span> <span class="n">Text</span><span class="o">(</span>
<span class="s">'Say Hello'</span><span class="o">,</span>
<span class="nl">textDirection:</span> <span class="n">TextDirection</span><span class="o">.</span><span class="na">ltr</span><span class="o">,</span>
<span class="o">),</span>
<span class="nl">onPressed:</span> <span class="kc">null</span><span class="o">,</span>
<span class="o">)</span>
<span class="o">],</span>
<span class="o">)),</span>
<span class="o">),</span>
<span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>
<p><img src="/assets/images/flutter-hello-world-4.png" alt="Hello world 4" />
从代码中我们可以看到我们增加了一个 <code class="language-plaintext highlighter-rouge">Column</code> 来实现一个多个 widgets 的竖状排列,所以它的子控件 属性变成了需要一个 List 的 <code class="language-plaintext highlighter-rouge">children</code>。我们再来看 Button,这里我们要给 button 添加一个说明,同样通过添加一个 <code class="language-plaintext highlighter-rouge">Text</code> widget 来实现。如果你耐心看到这里,你基本上已经入门了 Flutter 开发。再复杂的页面也是类似于这样组合 widget 的方式来进行开发,可能使用到更复杂的 widget 罢了。</p>
<p>你有没有发现,我们没有用 <code class="language-plaintext highlighter-rouge">xml</code> 或者 <code class="language-plaintext highlighter-rouge">xib</code> 文件,轻轻松松就实现了这样一个界面。 如果我们同样使用 Java 或者 Swift 来实现同样的界面,需要多少代码呢?</p>Trevor Wang介绍Flutter 项目结构介绍2018-06-09T00:00:00+00:002018-06-09T00:00:00+00:00https://trevorwang.github.io/2018/06/09/flutter-project-structure<h1 id="flutter-项目结构介绍">Flutter 项目结构介绍</h1>
<h2 id="主要目录以及文件">主要目录以及文件</h2>
<p><img src="/assets/images/pic-flutter-project-structure.png" alt="" /></p>
<ol>
<li><code class="language-plaintext highlighter-rouge">android</code> Android 平台相关代码</li>
<li><code class="language-plaintext highlighter-rouge">ios</code> iOS 平台相关代码</li>
<li><code class="language-plaintext highlighter-rouge">build</code> 项目编译产生的目录,不需要关心</li>
<li><code class="language-plaintext highlighter-rouge">lib</code> 跨平台代码,也是 Flutter 项目主要关心的目录</li>
<li><code class="language-plaintext highlighter-rouge">test</code> 测试相关代码</li>
<li><code class="language-plaintext highlighter-rouge">pubspec.yaml</code> 项目描述文件,相当于 NodeJs 项目的 <code class="language-plaintext highlighter-rouge">package.json</code>,里面包含了项目的描述信息以及所需要的依赖的库</li>
</ol>
<h2 id="主要文件说明">主要文件说明</h2>
<h3 id="pubspecyaml"><code class="language-plaintext highlighter-rouge">pubspec.yaml</code></h3>
<p><img src="/assets/images/pic-flutter-pubspec.png" alt="" /></p>
<h3 id="maindart"><code class="language-plaintext highlighter-rouge">main.dart</code></h3>
<p>Flutter 使用 <code class="language-plaintext highlighter-rouge">dart</code> 语言(以后的章节中会逐步增加一些介绍 dart 的内容)来进行开发,下图是将代码简化之后的效果。只需要3个类就可以实现一个 app 页面,并且可以同时运行在 iOS 和 Android 两个平台,非常简单。而且 <code class="language-plaintext highlighter-rouge">main.dart</code> 是所有 Flutter 项目的入口文件,不能被删除或者改名。
<img src="/assets/images/pic-flutter-main.png" alt="pic-flutter-main" /></p>Trevor WangFlutter 项目结构介绍 主要目录以及文件Flutter 开发环境配置2018-06-03T00:00:00+00:002018-06-03T00:00:00+00:00https://trevorwang.github.io/2018/06/03/how-to-setup-development-environment<h2 id="系统要求">系统要求</h2>
<p>操作系统:macOS (64-bit) 本文以 macOS 系统为例介绍安装过程<br />
工具: <code class="language-plaintext highlighter-rouge">bash</code>,<code class="language-plaintext highlighter-rouge">git</code>,<code class="language-plaintext highlighter-rouge">curl</code>,<code class="language-plaintext highlighter-rouge">unzip</code></p>
<h2 id="获取-flutter-sdk">获取 Flutter SDK</h2>
<ol>
<li>
<p>从<a href="https://flutter.io/sdk-archive/#macos">Flutter SDK Archive</a>页面下载最新版本的 SDK。
点击<a href="https://storage.googleapis.com/flutter_infra/releases/beta/macos/flutter_macos_v0.4.4-beta.zip">flutter_macos_v0.4.4-beta.zip</a>下载或者</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-o</span> ~/Downloads/flutter_macos_v0.4.4-beta.zip https://storage.googleapis.com/flutter_infra/releases/beta/macos/flutter_macos_v0.4.4-beta.zip
</code></pre></div> </div>
<p><strong>P.S. 如果上面链接有问题,请使用中国专用链接</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>curl <span class="nt">-o</span> ~/Downloads/flutter_macos_v0.4.4-beta.zip https://storage.flutter-io.cn/flutter_infra/releases/beta/macos/flutter_macos_v0.4.4-beta.zip
</code></pre></div> </div>
</li>
<li>
<p>解压文件到你想要的路径</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">cd</span> ~/development
unzip ~/Downloads/flutter_macos_v0.4.4-beta.zip
</code></pre></div> </div>
</li>
<li>
<p>添加<code class="language-plaintext highlighter-rouge">flutter</code>到PATH</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="sb">`</span><span class="nb">pwd</span><span class="sb">`</span>/flutter/bin:<span class="nv">$PATH</span>
</code></pre></div> </div>
</li>
<li>
<p>运行 flutter doctor</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>flutter doctor
</code></pre></div> </div>
</li>
</ol>
<p><strong><em>如果按照上面的步骤,没有成功安装。请采用下面命令安装:</em></strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">PUB_HOSTED_URL</span><span class="o">=</span>https://pub.flutter-io.cn
<span class="nb">export </span><span class="nv">FLUTTER_STORAGE_BASE_URL</span><span class="o">=</span>https://storage.flutter-io.cn
git clone <span class="nt">-b</span> dev https://github.com/flutter/flutter.git
<span class="nb">export </span><span class="nv">PATH</span><span class="o">=</span><span class="s2">"</span><span class="nv">$PWD</span><span class="s2">/flutter/bin:</span><span class="nv">$PATH</span><span class="s2">"</span>
<span class="nb">cd</span> ./flutter
flutter doctor
<span class="c">####备用的镜像网站####</span>
<span class="c"># export FLUTTER_STORAGE_BASE_URL=https://mirrors.sjtug.sjtu.edu.cn/</span>
<span class="c"># export PUB_HOSTED_URL=https://dart-pub.mirrors.sjtug.sjtu.edu.cn/</span>
</code></pre></div></div>
<h2 id="配置开发平台">配置开发平台</h2>
<h3 id="ios开发">iOS开发</h3>
<h4 id="安装-xcode">安装 Xcode</h4>
<ol>
<li>从 <a href="https://itunes.apple.com/us/app/xcode/id497799835">App Store</a> 安装 xcode.</li>
<li>
<p>配置命令行使用最新的 xcode</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">sudo </span>xcode-select <span class="nt">--switch</span> /Applications/Xcode.app/Contents/Developer
</code></pre></div> </div>
</li>
<li>
<p>确保您已经接受 xcode 许可协议</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="nb">sudo </span>xcodebuild <span class="nt">-license</span>
</code></pre></div> </div>
</li>
</ol>
<h4 id="配置模拟器">配置模拟器</h4>
<ol>
<li>
<p>通过LaunchPad 或者下面的命令来启动模拟器</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code>open <span class="nt">-a</span> Simulator
</code></pre></div> </div>
</li>
<li>
<p><code class="language-plaintext highlighter-rouge">flutter run</code> 来运行你的程序</p>
</li>
</ol>
<h3 id="android-开发">Android 开发</h3>
<h4 id="安装-android-studio">安装 Android Studio</h4>
<p>下载安装 <a href="https://developer.android.com/studio/index.html">Android Studio</a></p>
<h4 id="android-设备android-version--41">Android 设备(Android Version >= 4.1)</h4>
<ol>
<li>参照<a href="https://developer.android.com/studio/debug/dev-options.html">此链接</a>开启开发者模式并打开 debug</li>
<li>链接你的手机到电脑</li>
<li><code class="language-plaintext highlighter-rouge">flutter run</code> 运行你的程序</li>
</ol>Trevor Wang系统要求 操作系统:macOS (64-bit) 本文以 macOS 系统为例介绍安装过程 工具: bash,git,curl,unzipContinuous Delivery System Based on Gitlab2017-04-10T00:00:00+00:002017-04-10T00:00:00+00:00https://trevorwang.github.io/2017/04/10/Continuous-Delivery-System-Based-on-Gitlab<h2 id="setup-gitlab"><a href="https://docs.gitlab.com/omnibus/docker/README.html">Setup Gitlab</a></h2>
<p><strong>Run the following command to install gitlab on your machine.</strong></p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">sudo </span>docker run <span class="nt">-d</span> <span class="nt">--hostname</span> 10.211.55.5 <span class="se">\</span>
<span class="nt">-p</span> 80:80 <span class="nt">-p</span> 443:443 <span class="nt">-p</span> 22:22 <span class="se">\</span>
<span class="nt">--restart</span> always gitlab/gitlab-ce
</code></pre></div></div>
<blockquote>
<p>Please follow <a href="https://docs.docker.com/engine/installation/">official installation docs</a> to install docker if it’s not been installed.
Do NOT forget change <code class="language-plaintext highlighter-rouge">10.211.55.5</code> and <code class="language-plaintext highlighter-rouge">/srv/gitlab/</code> to your own.(I have a virtual machine with that ip.)
If your docker is behind a proxy, please see <a href="https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#newwindow=1&q=docker+behind+corporate+proxy">the search results from Google</a>.</p>
</blockquote>
<p><strong>After several mins, access <code class="language-plaintext highlighter-rouge">http://10.211.55.5</code> (the host you set above), you will the following picture.</strong>
<img src="/assets/images/pic-create-password.png" alt="pic-create-password" /></p>
<p><strong>Set your admin password, and login again.</strong></p>
<p><img src="/assets/images/pic-gitlab-homepage.png" alt="Gitlab Home Page" /></p>
<p><strong>Create new project <code class="language-plaintext highlighter-rouge">hello-world</code> as the following:</strong></p>
<p><img src="/assets/images/pic-gitlab-create-project.png" alt="Create new project" /></p>
<p>Once you create the project, follow the instruction to upload demo code to the repo. When you finish you will see a similar picture.</p>
<p><img src="/assets/images/pic-gitlab-project-setup-ci.png" alt="Gitlab project setup ci" /></p>
<p>Click the <code class="language-plaintext highlighter-rouge">Set up CI</code> button, add the following code and commit.</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="na">test</span><span class="pi">:</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">echo 'hello world'</span>
</code></pre></div></div>
<p>A pending icon will appear on the result page.</p>
<p><img src="/assets/images/pic-gitlab-commit-pending-to-run.png" alt="Gitlab commit pending to run" /></p>
<p>Click the icon, you will see a <code class="language-plaintext highlighter-rouge">stuk</code> tag on your commit. That means you have to add gitlab-runner to this project.</p>
<p><img src="/assets/images/pic-gitlab-pipeline-stuk.png" alt="" /></p>
<h2 id="setup-gitlab-runner">Setup Gitlab Runner</h2>
<ol>
<li>
<p>Open <code class="language-plaintext highlighter-rouge">CI/CD Pipeline</code> tab in project settings
<img src="/assets/images/pic-gitlab-project-ruuner-setup.png" alt="Gitlab project runner setup" /></p>
</li>
<li>
<p>Install setup runner on your local machine</p>
<div class="language-shell highlighter-rouge"><div class="highlight"><pre class="highlight"><code> brew <span class="nb">install </span>gitlab-ci-multi-runner
</code></pre></div> </div>
</li>
<li>
<p>Register your local machine as a runner to the gitlab</p>
<p><img src="/assets/images/pic-gitlab-runner-register.png" alt="Gitlab runner register" /></p>
</li>
<li>
<p>Runner list</p>
<p><img src="/assets/images/pic-gitlab-project-runner-list.png" alt="Project runner list" /></p>
</li>
<li>
<p>Go back to the <code class="language-plaintext highlighter-rouge">Pipelines</code> of your project and click the <code class="language-plaintext highlighter-rouge">retry</code> button</p>
<p><img src="/assets/images/pic-gitlab-pipelies-state.png" alt="" /></p>
</li>
<li>
<p>You runner has been set up successfully when you see the picture
<img src="/assets/images/pic-gitlab-pipeline-passed.png" alt="" /></p>
</li>
<li>
<p>Change the <code class="language-plaintext highlighter-rouge">.gitlab-ci.yml</code> to the following:</p>
<div class="language-yaml highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="na">test</span><span class="pi">:</span>
<span class="na">script</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">./gradlew aR</span>
<span class="na">artifacts</span><span class="pi">:</span>
<span class="na">paths</span><span class="pi">:</span>
<span class="pi">-</span> <span class="s">app/build/outputs/apk/app-release*.apk</span>
</code></pre></div> </div>
</li>
<li>
<p>Now you will get the release apk file from the download button.
<img src="/assets/images/pic-gitlab-pipeline-with-artifats.png" alt="" /></p>
</li>
</ol>Trevor WangSetup Gitlab Run the following command to install gitlab on your machine.Kotlin Null Safety2017-03-12T00:00:00+00:002017-03-12T00:00:00+00:00https://trevorwang.github.io/2017/03/12/Kotlin-Null-Safety<h1 id="kotlin-null-safety">Kotlin Null Safety</h1>
<blockquote>
<p>Kotlin’s type system is aimed to eliminate <code class="language-plaintext highlighter-rouge">NullPointerException</code>’s from our code. The only possible causes of NPE’s may be</p>
<ol>
<li>An explicit call to throw <code class="language-plaintext highlighter-rouge">NullPointerException()</code></li>
<li>Usage of the <code class="language-plaintext highlighter-rouge">!!</code> operator that is described below</li>
<li>External Java code has caused it</li>
<li>There’s some data inconsistency with regard to initialization (an uninitialized <code class="language-plaintext highlighter-rouge">this</code> available in a constructor is used somewhere)</li>
</ol>
</blockquote>
<h3 id="variables">Variables</h3>
<p>As mentioned above, there’re the only 4 ways to cause a NPE. And there’re nullable and non-nullable types in Kotlin’s System. So when you define a variable, you must tell Kotlin whether the variable is nullable or not.</p>
<ol>
<li>
<p>Non-nullable variable
You can define non-nullable variables as following:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">var</span> <span class="py">b</span><span class="p">:</span><span class="nc">String</span> <span class="p">=</span> <span class="s">"abc"</span>
<span class="kd">var</span> <span class="py">a</span><span class="p">:</span><span class="nc">Int</span> <span class="p">=</span> <span class="mi">10</span>
</code></pre></div> </div>
<p>Once you want to assign a <code class="language-plaintext highlighter-rouge">null</code> to a non-nullable variable,</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">b</span> <span class="p">=</span> <span class="k">null</span>
<span class="n">a</span> <span class="p">=</span> <span class="k">null</span>
</code></pre></div> </div>
<p>the compiler will throw you an error. The Kotlin system prevent nullable value in the compile stage.</p>
</li>
<li>
<p>Nullable variable
If you want to define a variable which can be set as <code class="language-plaintext highlighter-rouge">null</code>, please code as following.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">var</span> <span class="py">b</span><span class="p">:</span><span class="nc">String</span><span class="p">?</span> <span class="p">=</span> <span class="s">"abc"</span>
<span class="kd">var</span> <span class="py">a</span><span class="p">:</span><span class="nc">Int</span><span class="p">?</span> <span class="p">=</span> <span class="mi">10</span>
</code></pre></div> </div>
<p>Now both of them can be set as <code class="language-plaintext highlighter-rouge">null</code></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">b</span> <span class="p">=</span> <span class="k">null</span> <span class="c1">// ok</span>
<span class="n">a</span> <span class="p">=</span> <span class="k">null</span> <span class="c1">// ok</span>
</code></pre></div> </div>
</li>
</ol>
<h3 id="nullable-check">Nullable check</h3>
<p>The Kotlin system will tell you an error if you want to call a method from a nullable variable.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">b</span><span class="p">.</span><span class="n">length</span><span class="p">.</span> <span class="c1">// compile error</span>
</code></pre></div></div>
<p>You must check <code class="language-plaintext highlighter-rouge">b</code> whether it is null or not.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">if</span> <span class="p">(</span><span class="n">b</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="p">{</span>
<span class="n">b</span><span class="p">.</span><span class="n">length</span>
<span class="p">}</span>
</code></pre></div></div>
<p>It’s a Java style way, is there a new method to handle this in Kotlin. Absolutely, yes. There’s a new and safe operator, <code class="language-plaintext highlighter-rouge">?.</code>, to be used in this state.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">val</span> <span class="py">a</span> <span class="p">=</span> <span class="n">b</span><span class="o">?.</span><span class="n">length</span>
</code></pre></div></div>
<p>It’s safe and never throws the <code class="language-plaintext highlighter-rouge">NullPointerException</code>. It will return a <code class="language-plaintext highlighter-rouge">null</code> for this invocation and never really call the <code class="language-plaintext highlighter-rouge">length</code> method. Isn’t a easy way to handle nullable variable?</p>
<blockquote>
<p>If you want the Java’s style to handle exception, just use <code class="language-plaintext highlighter-rouge">!!</code>.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">val</span> <span class="py">a</span> <span class="p">=</span> <span class="n">b</span><span class="o">!!</span><span class="p">.</span><span class="n">length</span>
</code></pre></div> </div>
<p>The above code may throws a <code class="language-plaintext highlighter-rouge">NullPointerException</code> during the runtime.</p>
</blockquote>
<p>There’s also another example from kotlin’s official site:</p>
<blockquote>
<p>Safe calls are useful in chains. For example, if Bob, an Employee, may be assigned to a Department (or not), that in turn may have another Employee as a department head, then to obtain the name of Bob’s department head (if any), we write the following:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">bob</span><span class="o">?.</span><span class="n">department</span><span class="o">?.</span><span class="n">head</span><span class="o">?.</span><span class="n">name</span>
</code></pre></div> </div>
<p>Such a chain returns a <code class="language-plaintext highlighter-rouge">null</code> if any of the properties in it is null.</p>
</blockquote>
<p>To perform a certain operation only for non-null values, you can use the safe call operator together with <code class="language-plaintext highlighter-rouge">let</code>:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">listWithNulls</span><span class="p">:</span> <span class="nc">List</span><span class="p"><</span><span class="nc">String</span><span class="err">?</span><span class="p">></span> <span class="p">=</span> <span class="nf">listOf</span><span class="p">(</span><span class="s">"A"</span><span class="p">,</span> <span class="k">null</span><span class="p">)</span>
<span class="k">for</span> <span class="p">(</span><span class="n">item</span> <span class="k">in</span> <span class="n">listWithNulls</span><span class="p">)</span> <span class="p">{</span>
<span class="n">item</span><span class="o">?.</span><span class="nf">let</span> <span class="p">{</span> <span class="nf">println</span><span class="p">(</span><span class="n">it</span><span class="p">)</span> <span class="p">}</span> <span class="c1">// prints A and ignores null</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="elvis-operator-">Elvis Operator (<code class="language-plaintext highlighter-rouge">?:</code>)</h3>
<p>You definitely meet the following issue: return the string’s length if non-null and just return <code class="language-plaintext highlighter-rouge">-1</code> when it’s <code class="language-plaintext highlighter-rouge">null</code></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">val</span> <span class="py">l</span><span class="p">:</span> <span class="nc">Int</span> <span class="p">=</span> <span class="k">if</span> <span class="p">(</span><span class="n">b</span> <span class="p">!=</span> <span class="k">null</span><span class="p">)</span> <span class="n">b</span><span class="p">.</span><span class="n">length</span> <span class="k">else</span> <span class="p">-</span><span class="mi">1</span>
</code></pre></div></div>
<p>Now there’s new operator.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="kd">val</span> <span class="py">l</span> <span class="p">=</span> <span class="n">b</span><span class="o">?.</span><span class="n">length</span> <span class="o">?:</span> <span class="p">-</span><span class="mi">1</span>
</code></pre></div></div>
<p>or</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">bob</span><span class="o">?.</span><span class="n">department</span><span class="o">?.</span><span class="n">head</span><span class="o">?.</span><span class="n">name</span> <span class="o">?:</span> <span class="s">"No Name"</span>
</code></pre></div></div>
<p>If the expression to the left of <code class="language-plaintext highlighter-rouge">?:</code> is not null, the elvis operator returns it, otherwise it returns the expression to the right. Note that the right-hand side expression is evaluated only if the left-hand side is null.</p>
<p><strong><em>Note that, since throw and return are expressions in Kotlin, they can also be used on the right hand side of the elvis operator. This can be very handy, for example, for checking function arguments:</em></strong></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">fun</span> <span class="nf">foo</span><span class="p">(</span><span class="n">node</span><span class="p">:</span> <span class="nc">Node</span><span class="p">):</span> <span class="nc">String</span><span class="p">?</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">parent</span> <span class="p">=</span> <span class="n">node</span><span class="p">.</span><span class="nf">getParent</span><span class="p">()</span> <span class="o">?:</span> <span class="k">return</span> <span class="k">null</span>
<span class="kd">val</span> <span class="py">name</span> <span class="p">=</span> <span class="n">node</span><span class="p">.</span><span class="nf">getName</span><span class="p">()</span> <span class="o">?:</span> <span class="k">throw</span> <span class="nc">IllegalArgumentException</span><span class="p">(</span><span class="s">"name expected"</span><span class="p">)</span>
<span class="c1">// ...</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="safe-cast">safe cast</h3>
<p>Regular casts may result into a ClassCastException if the object is not of the target type. Another option is to use safe casts that return null if the attempt was not successful:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">a</span> <span class="p">=</span> <span class="nc">Animal</span><span class="p">()</span>
<span class="kd">val</span> <span class="py">dog</span><span class="p">:</span> <span class="nc">Dog</span><span class="p">?</span> <span class="p">=</span> <span class="n">a</span> <span class="k">as</span><span class="p">?</span> <span class="nc">Dog</span>
</code></pre></div></div>
<h3 id="collections-of-nullable-type">Collections of Nullable Type</h3>
<p>If you have a collection of elements of a nullable type and want to filter non-null elements, you can do so by using filterNotNull.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">val</span> <span class="py">nullableList</span><span class="p">:</span> <span class="nc">List</span><span class="p"><</span><span class="nc">Int</span><span class="err">?</span><span class="p">></span> <span class="p">=</span> <span class="nf">listOf</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="k">null</span><span class="p">,</span> <span class="mi">4</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">intList</span><span class="p">:</span> <span class="nc">List</span><span class="p"><</span><span class="nc">Int</span><span class="p">></span> <span class="p">=</span> <span class="n">nullableList</span><span class="p">.</span><span class="nf">filterNotNull</span><span class="p">()</span>
</code></pre></div></div>Trevor WangKotlin Null SafetyKotlin & Dagger22017-02-17T00:00:00+00:002017-02-17T00:00:00+00:00https://trevorwang.github.io/2017/02/17/kotlin-with-dagger2<blockquote>
<p><strong><em>The project is based on the code complete in last article. You can download from <a href="https://github.com/trevorwang/android-kotlin-example/tree/rxjava-retrofit">here</a></em></strong></p>
</blockquote>
<h3 id="add-dependencies-to-your-buildgradle">Add Dependencies to your <code class="language-plaintext highlighter-rouge">build.gradle</code></h3>
<div class="language-gradle highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="n">kapt</span> <span class="s1">'com.google.dagger:dagger-compiler:2.9'</span>
<span class="n">compile</span> <span class="s1">'com.google.dagger:dagger:2.9'</span>
</code></pre></div></div>
<h3 id="create-your-application-instead-of-default">Create your application instead of default</h3>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">App</span> <span class="p">:</span> <span class="nc">Application</span><span class="p">()</span> <span class="p">{</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">()</span> <span class="p">{</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="update-your-manifest-file-to-enable-your-app">Update your manifest file to enable your App</h3>
<p>Add the following to your <code class="language-plaintext highlighter-rouge">application</code> tag</p>
<div class="language-diff highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <application
<span class="gi">+ android:name=".App"
</span> android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
</code></pre></div></div>
<h3 id="create-application-module">Create Application module</h3>
<p>Create a <code class="language-plaintext highlighter-rouge">AppModule.kt</code> file and add the following code</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Module</span>
<span class="kd">class</span> <span class="nc">AppModule</span><span class="p">(</span><span class="kd">val</span> <span class="py">app</span><span class="p">:</span> <span class="nc">App</span><span class="p">)</span> <span class="p">{</span>
<span class="nd">@Provides</span>
<span class="nd">@Singleton</span>
<span class="k">fun</span> <span class="nf">app</span><span class="p">():</span> <span class="nc">App</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">app</span>
<span class="p">}</span>
<span class="nd">@Provides</span>
<span class="nd">@Singleton</span>
<span class="k">fun</span> <span class="nf">context</span><span class="p">():</span> <span class="nc">Context</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">app</span>
<span class="p">}</span>
<span class="nd">@Provides</span>
<span class="nd">@Singleton</span>
<span class="k">fun</span> <span class="nf">connectivity</span><span class="p">():</span> <span class="nc">ConnectivityManager</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">service</span> <span class="p">=</span> <span class="n">app</span><span class="p">.</span><span class="nf">getSystemService</span><span class="p">(</span><span class="nc">Context</span><span class="p">.</span><span class="nc">CONNECTIVITY_SERVICE</span><span class="p">)</span> <span class="k">as</span> <span class="nc">ConnectivityManager</span>
<span class="k">return</span> <span class="n">service</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="create-component">Create component</h3>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Singleton</span>
<span class="nd">@Component</span><span class="p">(</span>
<span class="n">modules</span> <span class="p">=</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="nc">AppModule</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="p">)</span>
<span class="kd">interface</span> <span class="nc">AppComponent</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">inject</span><span class="p">(</span><span class="n">app</span><span class="p">:</span> <span class="nc">App</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<h3 id="update-your-code-in-appkt-to-enable-the-injection">Update your code in <code class="language-plaintext highlighter-rouge">App.kt</code> to enable the injection</h3>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">component</span><span class="p">:</span> <span class="nc">AppComponent</span>
<span class="k">lateinit</span> <span class="nd">@Inject</span> <span class="kd">var</span> <span class="py">cm</span><span class="p">:</span> <span class="nc">ConnectivityManager</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">()</span>
<span class="n">component</span> <span class="p">=</span> <span class="nc">DaggerAppComponent</span><span class="p">.</span><span class="nf">builder</span><span class="p">().</span><span class="nf">appModule</span><span class="p">(</span><span class="nc">AppModule</span><span class="p">(</span><span class="k">this</span><span class="p">)).</span><span class="nf">build</span><span class="p">()</span>
<span class="n">component</span><span class="p">.</span><span class="nf">inject</span><span class="p">(</span><span class="k">this</span><span class="p">)</span>
<span class="nc">Logger</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="s">"connectivity manager : ${cm}"</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now you can run your code in IDE, you will see the following in your console. You have completed your first injection for Application.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>02-17 10:03:20.492 10572-10572/? D/PRETTYLOGGER: ╔════════════════════════════════════════════════════════════════════════════════════════
02-17 10:03:20.493 10572-10572/? D/PRETTYLOGGER: ║ Thread: main
02-17 10:03:20.493 10572-10572/? D/PRETTYLOGGER: ╟────────────────────────────────────────────────────────────────────────────────────────
02-17 10:03:20.493 10572-10572/? D/PRETTYLOGGER: ║ BootstrapApplication.onCreate (BootstrapApplication.java:370)
02-17 10:03:20.493 10572-10572/? D/PRETTYLOGGER: ║ App.onCreate (App.kt:21)
02-17 10:03:20.493 10572-10572/? D/PRETTYLOGGER: ╟────────────────────────────────────────────────────────────────────────────────────────
02-17 10:03:20.493 10572-10572/? D/PRETTYLOGGER: ║ connectivity manager : android.net.ConnectivityManager@39309b5
02-17 10:03:20.494 10572-10572/? D/PRETTYLOGGER: ╚════════════════════════════════════════════════════════════════════════════════════════
</code></pre></div></div>
<h3 id="follow-above-guide-to-create-activitymodule-and-activitycomponent">Follow above guide to create <code class="language-plaintext highlighter-rouge">ActivityModule</code> and <code class="language-plaintext highlighter-rouge">ActivityComponent</code></h3>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Module</span>
<span class="kd">class</span> <span class="nc">ActivityModule</span><span class="p">(</span><span class="kd">val</span> <span class="py">activity</span><span class="p">:</span> <span class="nc">DaggerActivity</span><span class="p">)</span> <span class="p">{</span>
<span class="nd">@Provides</span>
<span class="k">fun</span> <span class="nf">activity</span><span class="p">():</span> <span class="nc">DaggerActivity</span> <span class="p">{</span>
<span class="k">return</span> <span class="n">activity</span>
<span class="p">}</span>
<span class="nd">@Provides</span>
<span class="k">fun</span> <span class="nf">progressDialog</span><span class="p">():</span> <span class="nc">ProgressDialog</span> <span class="p">{</span>
<span class="kd">val</span> <span class="py">progress</span> <span class="p">=</span> <span class="nc">ProgressDialog</span><span class="p">(</span><span class="n">activity</span><span class="p">)</span>
<span class="n">progress</span><span class="p">.</span><span class="nf">setMessage</span><span class="p">(</span><span class="n">activity</span><span class="p">.</span><span class="nf">getString</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">string</span><span class="p">.</span><span class="n">loading</span><span class="p">))</span>
<span class="k">return</span> <span class="n">progress</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@ForActivity</span>
<span class="nd">@Component</span><span class="p">(</span>
<span class="n">modules</span> <span class="p">=</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="nc">ActivityModule</span><span class="o">::</span><span class="k">class</span><span class="p">),</span>
<span class="n">dependencies</span> <span class="p">=</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="nc">AppComponent</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="p">)</span>
<span class="kd">interface</span> <span class="nc">ActivityComponent</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">inject</span><span class="p">(</span><span class="n">activity</span><span class="p">:</span> <span class="nc">MainActivity</span><span class="p">)</span>
<span class="p">}</span>
</code></pre></div></div>
<p>You definately find there’s a <code class="language-plaintext highlighter-rouge">@ForActivity</code> above the <code class="language-plaintext highlighter-rouge">ActivityComponent</code>. Dagger need you to set a scope to define the lifecycle for any component. Activity cannot be the singleton. So we define a scrope annotation called ‘ForActivity’.</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Qualifier</span>
<span class="nd">@Retention</span><span class="p">(</span><span class="n">value</span> <span class="p">=</span> <span class="nc">AnnotationRetention</span><span class="p">.</span><span class="nc">RUNTIME</span><span class="p">)</span>
<span class="nd">@Scope</span>
<span class="k">annotation</span> <span class="kd">class</span> <span class="nc">ForActivity</span>
</code></pre></div></div>
<h3 id="use-activity-component-in-your-code">Use activity component in your code</h3>
<p>Create a base class called <code class="language-plaintext highlighter-rouge">DaggerActivity</code></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">abstract</span> <span class="kd">class</span> <span class="nc">DaggerActivity</span> <span class="p">:</span> <span class="nc">AppCompatActivity</span><span class="p">()</span> <span class="p">{</span>
<span class="kd">var</span> <span class="py">component</span><span class="p">:</span> <span class="nc">ActivityComponent</span><span class="p">?</span> <span class="p">=</span> <span class="k">null</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">app</span> <span class="p">=</span> <span class="n">application</span> <span class="k">as</span> <span class="nc">App</span>
<span class="n">component</span> <span class="p">=</span> <span class="nc">DaggerActivityComponent</span><span class="p">.</span><span class="nf">builder</span><span class="p">()</span>
<span class="p">.</span><span class="nf">activityModule</span><span class="p">(</span><span class="nc">ActivityModule</span><span class="p">(</span><span class="k">this</span><span class="p">))</span>
<span class="p">.</span><span class="nf">appComponent</span><span class="p">(</span><span class="n">app</span><span class="p">.</span><span class="n">component</span><span class="p">)</span>
<span class="p">.</span><span class="nf">build</span><span class="p">()</span>
<span class="nf">onInject</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">abstract</span> <span class="k">fun</span> <span class="nf">onInject</span><span class="p">()</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onDestroy</span><span class="p">()</span> <span class="p">{</span>
<span class="n">component</span> <span class="p">=</span> <span class="k">null</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onDestroy</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Update your <code class="language-plaintext highlighter-rouge">MainActivity</code> to extends <code class="language-plaintext highlighter-rouge">DaggerActivity</code></p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">MainActivity</span> <span class="p">:</span> <span class="nc">DaggerActivity</span><span class="p">()</span> <span class="p">{</span>
<span class="nd">@Inject</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">apiService</span><span class="p">:</span> <span class="nc">ApiService</span>
<span class="nd">@Inject</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">cm</span><span class="p">:</span> <span class="nc">ConnectivityManager</span>
<span class="nd">@Inject</span> <span class="k">lateinit</span> <span class="kd">var</span> <span class="py">progressDialog</span><span class="p">:</span> <span class="nc">ProgressDialog</span>
<span class="k">private</span> <span class="kd">val</span> <span class="py">subscriptions</span> <span class="p">=</span> <span class="nc">CompositeSubscription</span><span class="p">()</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">:</span> <span class="nc">Bundle</span><span class="p">?)</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onCreate</span><span class="p">(</span><span class="n">savedInstanceState</span><span class="p">)</span>
<span class="nf">setContentView</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span>
<span class="kd">val</span> <span class="py">binding</span> <span class="p">=</span> <span class="nc">DataBindingUtil</span><span class="p">.</span><span class="n">setContentView</span><span class="p"><</span><span class="nc">ActivityMainBinding</span><span class="p">>(</span><span class="k">this</span><span class="p">,</span> <span class="nc">R</span><span class="p">.</span><span class="n">layout</span><span class="p">.</span><span class="n">activity_main</span><span class="p">)</span>
<span class="n">binding</span><span class="p">.</span><span class="n">greating</span> <span class="p">=</span> <span class="s">"Hello Kotlin Data Binding!!!"</span>
<span class="kd">val</span> <span class="py">toolbar</span> <span class="p">=</span> <span class="nf">findViewById</span><span class="p">(</span><span class="nc">R</span><span class="p">.</span><span class="n">id</span><span class="p">.</span><span class="n">toolbar</span><span class="p">)</span> <span class="k">as</span> <span class="nc">Toolbar</span>
<span class="nf">setSupportActionBar</span><span class="p">(</span><span class="n">toolbar</span><span class="p">)</span>
<span class="nc">Logger</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="n">cm</span><span class="p">)</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onInject</span><span class="p">()</span> <span class="p">{</span>
<span class="n">component</span><span class="o">?.</span><span class="nf">inject</span><span class="p">(</span><span class="k">this</span><span class="p">)</span> <span class="c1">// must call this</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onResume</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onResume</span><span class="p">()</span>
<span class="n">progressDialog</span><span class="p">.</span><span class="nf">show</span><span class="p">()</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onStart</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onStart</span><span class="p">()</span>
<span class="n">subscriptions</span><span class="p">.</span><span class="nf">add</span><span class="p">(</span><span class="n">apiService</span><span class="p">.</span><span class="nf">ip</span><span class="p">().</span><span class="nf">subscribeOn</span><span class="p">(</span><span class="nc">Schedulers</span><span class="p">.</span><span class="nf">io</span><span class="p">())</span>
<span class="p">.</span><span class="nf">observeOn</span><span class="p">(</span><span class="nc">AndroidSchedulers</span><span class="p">.</span><span class="nf">mainThread</span><span class="p">())</span>
<span class="p">.</span><span class="nf">subscribe</span><span class="p">({</span>
<span class="n">ip</span> <span class="p">-></span>
<span class="nc">Logger</span><span class="p">.</span><span class="nf">d</span><span class="p">(</span><span class="s">"get result $ip"</span><span class="p">)</span>
<span class="n">progressDialog</span><span class="p">.</span><span class="nf">hide</span><span class="p">()</span>
<span class="p">})</span> <span class="p">{</span> <span class="n">error</span> <span class="p">-></span>
<span class="nc">Logger</span><span class="p">.</span><span class="nf">e</span><span class="p">(</span><span class="n">error</span><span class="p">,</span> <span class="s">"Aha.. got error message"</span><span class="p">)</span>
<span class="n">progressDialog</span><span class="p">.</span><span class="nf">hide</span><span class="p">()</span>
<span class="p">})</span>
<span class="p">}</span>
<span class="k">override</span> <span class="k">fun</span> <span class="nf">onStop</span><span class="p">()</span> <span class="p">{</span>
<span class="k">super</span><span class="p">.</span><span class="nf">onStop</span><span class="p">()</span>
<span class="n">subscriptions</span><span class="p">.</span><span class="nf">clear</span><span class="p">()</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Run code from you IDE, you will see the fowlloing error</p>
<pre><code class="language-txt">Error:mingsin.androidkotlinexample.data.ApiService cannot be provided without an @Provides-annotated method.
Error:mingsin.androidkotlinexample.data.ApiService cannot be provided without an @Provides- or @Produces-annotated method.
Error:android.net.ConnectivityManager cannot be provided without an @Inject constructor or from an @Provides- or @Produces-annotated method.
</code></pre>
<p>Because <code class="language-plaintext highlighter-rouge">ConnectivityManager </code> and <code class="language-plaintext highlighter-rouge">ApiService </code> are provided in you app component, So we must tell the dagger that we provide the two instance.
Update your <code class="language-plaintext highlighter-rouge">AppComponent</code> code as following:</p>
<div class="language-kotlin highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Singleton</span>
<span class="nd">@Component</span><span class="p">(</span>
<span class="n">modules</span> <span class="p">=</span> <span class="nf">arrayOf</span><span class="p">(</span><span class="nc">AppModule</span><span class="o">::</span><span class="k">class</span><span class="p">)</span>
<span class="p">)</span>
<span class="kd">interface</span> <span class="nc">AppComponent</span> <span class="p">{</span>
<span class="k">fun</span> <span class="nf">inject</span><span class="p">(</span><span class="n">app</span><span class="p">:</span> <span class="nc">App</span><span class="p">)</span>
<span class="k">fun</span> <span class="nf">connectivity</span><span class="p">():</span> <span class="nc">ConnectivityManager</span>
<span class="k">fun</span> <span class="nf">api</span><span class="p">():</span> <span class="nc">ApiService</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Now you have complete a project integrated with dagger2. You can chekcout the whole project from <a href="https://github.com/trevorwang/android-kotlin-example/tree/kotlin-dagger">here</a></p>
<h4 id="references">References</h4>
<ol>
<li><a href="https://google.github.io/dagger/">Dagger2</a></li>
<li><a href="https://medium.com/@methodsignature/dagger-2-dependency-injection-for-android-developers-51d60e7397e6#.f4yp32sbb">Dagger 2 Dependency Injection for Android Developers</a></li>
<li><a href="https://github.com/codepath/android_guides/wiki/Dependency-Injection-with-Dagger-2">Dependency Injection with Dagger 2</a></li>
</ol>Trevor WangThe project is based on the code complete in last article. You can download from here