#dartlang_jp のFlutterハンズオンで再度Flutterを触ってみた

この前の日曜日にFlutterのハンズオンに参加してきました。

dartisans-jp.connpass.com

  • 講師
    • ntaooさん 読み方は特に決めてないそうですが、「えぬたおさん」でいいとのこと。
  • 資料

docs.google.com

docs.google.com

dart自体から実際のFlutterアプリまで、かなり盛りだくさんな内容でした。

以前に一度公式のtutorialはこなしていたのですが、プラスαで学べる事が多かったのでまとめておきます。

講義

Dart

FlutterからDartを知ったのですが、Dart自体はもともとWeb用の言語だったそうです。2004年頃からWeb以外もターゲットにした汎用プログラミング言語としての道を歩み始めたとのこと。
また、開発時はJIT, リリース時はAOTコンパイルを実行する点、言語レベルで非同期処理を実装している点などが特徴みたいです。
全体的な文法はJavaとJSにそっくりです。言語自体は意図的に面白くなくしており、簡単に習得できるように設計されています。

Flutter

Flutter自体は前回も触れましたが、復習もかねて。

  • 特徴
  • 構造
    • coreとなるEngine部分はC++で書かれている
    • documentもしっかりある
  • パッケージ管理システム
    • Pub package manager
    • https://pub.dartlang.org/
    • ユニバーサルに使えるコアパッケージと各プラットフォーム向けに使えるパッケージがある

参照

ハンズオン

作ったもの

f:id:t-miliya612:20180520220251p:plain

hacker news(https://news.ycombinator.com/)のニュースリーダーです。実際に外部APIを叩いています。
f:id:t-miliya612:20180519190553j:plain

ニュースをタップすると、WebViewが開いてページを表示できます。
f:id:t-miliya612:20180519190644j:plain

また、ニュースをお気に入りに登録できます。お気に入りリストも見ることが出来ます。
f:id:t-miliya612:20180519190618j:plain

さらに、Pull(Swipe?)-To-Rrefreshも実装。ネイティブをやっていく自信がなくなってきますね。
f:id:t-miliya612:20180519191216j:plain

ゆっくり調べながらやっていたら当日終わらなくて、後日改めてやり直したコードがこちら。コメント多めです。

github.com

学び

ほんとに何から何までWidgetであること

特にDividerなんて顕著ですが、marginから何から全部がWidgetとして扱われます。HTML書いている時にpaddingにするかmarginにするか悩むことが多かったのですが、Flutterだとその辺何も考えずに決められそうでいい感じです。

ネイティブのUIの充実

アイコンとPullToRefreshで実感しました。今回はボタンなどは使用していないためあんまりMaterial感を体験できていないのですが、ネイティブで書くより互換性を気にせずもりもり書けるのはちょっと爽快感があります。 ちなみに、AndroidminSdkVersionはデフォルトで16に設定されていました。環境によって違うのでしょうか。
また、アイコンですが、Iconsクラス配下に大量に用意されています。以下のサイトに一覧があるのですが、数えてみたら 985個もありました。笑う。

docs.flutter.io

つらかったところ

書いていくうちに、インデントとカッコの対応が崩れた瞬間の絶望感。。他の言語よりも崩れたときの復帰がしんどい印象でした。Android StudioのDartAnalysisを使えばかなりわかりやすくなりますが、これがなかったらと思うと……恐ろしいですね。
f:id:t-miliya612:20180519190824p:plain

正直初めは何やってるのか全く分かりませんでしたが、2回目をやり直したらだいぶ目が慣れてきました。 marginからdividerから何までWidgetであるということを忘れなければ大丈夫だと思います。
また、やっぱりネイティブと比べてしまうと不安定なところはあります。例えば、ListViewを勢い良くスクロールすると……たまに落ちたします。この辺のキャパシティというか、何も考えずに書いてもある程度いい感じにやってくれるのは、もしかするとネイティブの方なのかなぁ、などと。

lintについて

ハンズオンでは参考程度くらいでしか触れられていないのですが、Flutter(Dart)のlintについてです。
デフォルトでは、override時のアノテーション(@override)は添えていなくてもエラーが出ないようになっています。ただ、慣れていないうちにタイポ等もろもろつらかったので、試しに追加してみました。手順は以下の通りです。

  1. projectのrootにanalysis_options.dartを作成する。
    • これ、もとは.analysis_optionsだったみたいなのですが、変更加えるそうなので前者がおすすめだそうです。
  2. 中身をこんな感じに書く。
analyzer:
  strong-mode: true
  errors:
    todo: ignore
  exclude:
    - flutter/**
    - lib/api/*.dart

linter:
  rules:
    - avoid_empty_else
    - cancel_subscriptions
    - close_sinks
    - annotate_overrides

以上です。簡単ですが、忘れず設定しておきたいです。
詳しくは、以下のリンクを参照してください。

www.dartlang.org

Linter for Dart

感想

Flutter公式のtutorialにプラスした部分もあり、かなり実用的なハンズオンでした。このアプリ普通に使えますね……。そして、何故か忘れがちですがこれがAndroidiOS両方で動いているというの、改めて恐ろしいです。
ただ、やっぱりそろそろファイル1枚に書いていくスタイルが厳しくなってくる分量だと思うので、まずはこれを分割していくところが次のステップでしょうか。
また、今後はチャットアプリなど外部のAPIにPOSTしていくようなものも触ってみたいです。次は話題のFirebaseですかね……!
講師のntaooさん、主催の方々、ありがとうございました!


追記

講師のntaooさんよりコメントいただきました!
コーディングの際に、プラグインのおかげで強力なalt+enterの補完が使えるそうです。詳しくは以下のリンクとこの記事のコメント欄を参照してください。

flutter.io

GoogleI/O行ってないけどAndroid Jetpack気になったので調べてみた

先週はGoogle I/Oでしたね。現地にも行かずリアルタイムで見ることもしませんでしたが、TwitterのTLから現地のレポートを見てそわそわしていました。
KeynoteでのAndroid P発表も騒がれましたが、何やらJetpackとやらが話題の様子。Googleがdocumentを揃えてくれているので、読んでみました。

なお、翻訳ではなくメモ程度です

developer.android.com

What’s

公式サイトによると、

Jetpack is a set of libraries, tools and architectural guidance to help make it quick and easy to build great Android apps. 

とのこと。後述する内訳も見ると、これまでGoogleが提供していたArchitectureComponentを含んだコンポーネント群、のような感じでしょうか。

What’s included

4つの区分があるようです。

Foundation

Android開発の全領域に関わるコンポーネントが入っています。AppCompatよく分からんけど使っとくか、みたいな時期がすごく長かったので、個人的にこれがちゃんと分類されたことはすごく嬉しいです。

Architecture

  • Data Binding
  • Lifecycles
  • LiveData
  • Navigation
  • Paging
  • Room

ここがいわゆるArchitecture Componentが入るカテゴリのようですね。データの永続化、データ変更の通知などを行うものから、ライフサイクルのつらみをラッピングしてくれるものまで詰まっています。 正直まだ全部は分かってないので詳しくは書きません。
Navigationのところは、Androidアプリのデザイン自体とも絡んできそうなので、早めに読みたいです。

Behavior

  • Download manager
  • Media & playback
  • Notifications
  • Permissions
  • Sharing
  • Slices

制作しているアプリが、他のアプリやOSなどと上手くやっていくところをお手伝いしてくれるコンポーネント群、という理解です。
特に気になったのはNotificationsとSlicesです。前に業務でウィジェット(ホームスクリーンに出てくる方)を作ることがあったのですが、通知やらUI微妙に作りづらい問題やらで結構苦労しました。この辺がきれいに作れるようになってるといいなぁ……。(まだ見てない)

UI

  • Animation & transitions
  • Auto
  • Emoji
  • Fragment
  • Layout
  • Palette
  • TV
  • Wear OS by Google

当たり前なのですが、UIというだけあって、handset以外のモバイル端末から車までもをサポートするコンポーネントが揃っています。スマホだけがAndroidアプリ開発じゃないんだ。
加えて、おなじみFragmentから絵文字、アニメーションを対象としたコンポーネントが入っています。

How to use

developer.android.com

具体的な使い方は各コンポーネント群をインストールして……となるのです が、Jetpackベースでアプリを作る時のハンズオン?のようなものも用意されていました。

Android Studioの3.2+が必須条件です。まだアイコンが緑色の人(2018/05/12時点)は、公式サイトからPreview版をインストールしましょう。 Stable版とは違う名前でインストールされたのでご安心を。

f:id:t-miliya612:20180513030135p:plain

次に、新しくプロジェクトを作ります。
すると……

f:id:t-miliya612:20180513030156p:plain

新顔がいますね。Activity & Fragment + ViewModelだそうです。

f:id:t-miliya612:20180513030213p:plain

ActivityやFragmentとそれらに紐づくlayout配下のXMLの名前を聞かれます。さらに、ViewModelの名前やFragment package pathを入力する項目がありますね。

とりあえずそのままにしてFinishを押すと……

f:id:t-miliya612:20180513030227p:plain

Activity, Fragment, ViewModelが出来ています。Activity以外はuiパッケージに押し込めてくれています。
ソースはかなりシンプルですが、以下のとおりです。

Activity

...
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main_activity)
        if (savedInstanceState == null) {
            supportFragmentManager.beginTransaction()
                    .replace(R.id.container, MainFragment.newInstance())
                    .commitNow()
        }
    }

}

Fragment

...
class MainFragment : Fragment() {

    companion object {
        fun newInstance() = MainFragment()
    }

    private lateinit var viewModel: MainViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View {
        return inflater.inflate(R.layout.main_fragment, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        // TODO: Use the ViewModel
    }

}

ViewModel

import android.arch.lifecycle.ViewModel

class MainViewModel : ViewModel() {
    // TODO: Implement the ViewModel
}

ページ上に進めていくと、ViewModelを実装したりArchitectureComponentのNavigationの香りを感じたりできます。

Guide to App Architecture

Guide to App Architecture  |  Android Developers

ちょっと長いので箇条書きで。

  • アプリのコンポーネントの中にアプリのデータや状態を保持してはいけない
    • 全てのアプリのコンポーネントは、個別に起動されたりいつでも捨てられる可能性があるため
  • コンポーネント間は依存せず独立しているべき
  • 重要な原理は2つ
    • 関心の分離をしよう
      • ActivityやFragmentからUI操作やOSとのやり取り以外のコードを排除する
    • UIを(できれば永続化した)モデルから操作しよう
      • OSによるリソース解放でアプリのプロセスがdestroyされたとしてもユーザーがデータを失わないため
      • ネットワークの状態がよくない時でもアプリを起動しておくため
      • Modelとはアプリ内のデータを扱うコンポーネント

UIを作る時は?

  • ViewModelを使おう
    • 表示するデータを保持しておくため
    • Viewや設定の変更(画面の向きの変更によるactivityの再生成など)とは関係なしにデータを扱える
  • LiveDataを使おう
    • アプリの他のコンポーネントから保持しているデータの変更を検知するため
    • データの変更をobserveしてUIを更新したいときには、propertyをLiveDataで包んだ後、observe()メソッド内で処理を行う

データを取得するときは?

  • REST APIと通信を行うライブラリを使おう
    • Serviceという形でメソッドを準備する
    • 今回はRetrofitを使う
  • ViewModelにデータの取得とオブジェクトへのマッピングの責務を追わせるのはやめよう
    • メンテナンス性向上のため
    • 関心の分離のため
  • Repositoryオブジェクトを作ろう
    • データの操作を担当する
    • データの取得先やデータ更新時に呼ぶAPIを知っている
    • data sourceを抽象化できる
    • (これはコンポーネントというより実装方針?)
  • コンポーネント間の依存性を管理しよう
    • RepositoryAPI通信用のServiceインスタンスが必要
    • 今後Repositoryを使う全クラスでServiceインスタンス生成を行うのは面倒
    • 方法は2つ
      • DIを使って他クラスから実行時にインスタンスを提供(注入)する
      • Service Locatorを使ってregistryを用意する
    • テスタビリティも向上する

ViewModelとRepositoryを接続するときは?

ViewModelからrepositoryのメソッド呼ぶだけです。次!

データをキャッシュしたいときは?

  • Fragmentに戻るたびにAPIをcallするのはやめよう
    • 貴重なネットワークの帯域を浪費することになる
    • ユーザーに通信完了まで待たせることになる
val cached = CacheObject.get(id)
if (cached != nil) {
    return cached
}

// 以降、APIからの取得処理

そっか、インメモリキャッシュってこんなシンプルだったんだと考え直しました。SQLiteやらrealmやらライブラリ先行で技術を追っていると大事なことを忘れてしまいますね。

データを永続化したいときは?

  • Roomを使おう
    • SQLiteのORマッパー
    • コンパイル時にSQLSchemeと合っているかバリデーションしてくれる
    • 使い方
      • local schemeとして、Entityとするクラスに@Entityアノテーションをつける
      • RoomDatabaseを拡張したdatabaseクラスを作成する
      • データ挿入用のDAOを生成する
      • databaseクラス内でDAOへの参照を作る

テストしたいときは?

  • UI & Interaction
    • この時だけEspressoなどを使ってUIの実装のテストをしよう
  • ViewModel
    • JUnitでテストをしよう
    • Repositoryをモックににする必要がある
  • Repository
    • JUnitでテストをしよう
    • ServiceとDAOをモックにする必要がある
    • 以下のことを確かめよう
      • 正しくServiceのメソッドをcallしているか
      • APIの結果を正しくDBに保存しているか
      • データがキャッシュされていて最新のとき、余計なAPIリクエストをしていないか
  • DAO
    • instrumentationテストを行う必要がある
    • JUnitSupportSQLiteOpenHelperを使えばUnitテストができるが、端末とローカルPCとでSQLiteのバージョンに違いがあると困る
  • Service
    • Mock化しよう
    • 外部に依存しないテストにするため、通信が走らないでテストをしよう
    • ライブラリはたくさんあるけど、okhttp/mockwebserverとかおすすめ
  • TestingArtifacts
    • ArchitectureComponentは、バックグラウンドのスレッドを操作するmavenアーティファクトを用意
      • InstantTaskExecutorRule: 全てのバックグラウンドの操作を行う
      • CountingTaskExecutorRule: instrumentationテスト内で、ArchitectureComponentのバックグラウンド処理を待ったりEspressoと接続したりする

TODO

  • 古くなったデータの更新処理
  • ネットワークの状態をユーザーに通知する

Note

  • データの整合性を保つには、data sourceを1つにしよう
    • 同じデータを返す複数のAPIのエンドポイントがあるとき、データの齟齬が発生する
    • 全てのデータ取得を内部のDBなどに統一すれば解決できる

大筋は以上です!!長かった!~後半JetpackというよりはAACの話だけどまあいっか~
実際はコードラボ風味になっているので、手を動かしながら進めていただきたく!

AACの紹介ではなくて、直面した問題ベースでの設計の考え方をベースに進めてくれたので、かなり分かりやすいし研修の教材としてもいい感じなのではないでしょうか。
まだAACで触わりたりないところがあるので、各コンポーネントの内部もしっかり読んでいきたいです。

Android StudioのActivityTemplate全部試してみた!(2/2)

前回に引き続き、AndroidStudioのActivityのサンプルを試してみます。 残り6個!

目次

  • LoginActivity
  • Master/Detail Flow
  • TabbedActivity
  • SettingsActivity
  • ScrollingActivity
  • NavigationDrawer

LoginActivity

f:id:t-miliya612:20180510205338p:plain

特徴

  • 一般的なログインフォーム
  • 補完機能などUIが充実

Source

xml

<!-- プログレスバー -->
<ProgressBar
    android:id="@+id/login_progress"
    style="?android:attr/progressBarStyleLarge"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginBottom="8dp"
    android:visibility="gone" />
<!-- メールアドレスの補完機能がついたTextView -->
<AutoCompleteTextView
    android:id="@+id/email"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:hint="@string/prompt_email"
    android:inputType="textEmailAddress"
    android:maxLines="1"
    android:singleLine="true" />

自動補完用のTextViewなんてあったんですね。

Activity

長いのでメソッド名だけ、

  • permission処理
    • mayRequestContacts()
    • onRequestPermissionResult(Int, Array, IntArray)
  • 連絡帳アクセス
    • onCreateLoader(Int, Bundle?)
    • onLoadFinished(Loader>Cursor>, Cursor)
  • textViewに補完
    • addEmailsToAutoComplete(List)
  • 通信処理
    • inner class UserLoginTask
  • プログレスダイアログ
    • showProgress(Boolean)

機能だけでなく、UX的な面でも参考になります……!

Master/Detail Flow

f:id:t-miliya612:20180510212411p:plain

特徴

  • リストをタップすると詳細画面が表示される
  • スマホだと画面遷移、タブレットだとペイン表示

Source

基本は一般的なActivityとFragmentです。特徴的なのは、出し分けのロジックですね。
今回はこれを2つのitem_list.xmlの呼び分けで実現しています。app/src/main/res/layoutにあるものが呼ばれていればスマホサイズ、app/src/main/res/layout-w900dpのものが呼ばれていればタブレットサイズとなります。

Activity

if (item_detail_container != null) {
    // The detail container view will be present only in the
    // large-screen layouts (res/values-w900dp).
    // If this view is present, then the
    // activity should be in two-pane mode.
    twoPane = true
}
onClickListener = View.OnClickListener { v ->
    val item = v.tag as DummyContent.DummyItem
    // タブレットかどうか
    if (twoPane) {
        val fragment = ItemDetailFragment().apply {
            arguments = Bundle().apply {
                putString(ItemDetailFragment.ARG_ITEM_ID, item.id)
            }
        }
        parentActivity.supportFragmentManager
                .beginTransaction()
                .replace(R.id.item_detail_container, fragment)
                .commit()
    } else {
        val intent = Intent(v.context, ItemDetailActivity::class.java).apply {
            putExtra(ItemDetailFragment.ARG_ITEM_ID, item.id)
        }
        v.context.startActivity(intent)
    }
}

TabbedActivity

特徴

3種類のスタイルがあります。

  • Swipe Views

f:id:t-miliya612:20180510204831p:plain

  • Action Bar Tabs

f:id:t-miliya612:20180510204917p:plain

  • Action Bar Spinner

f:id:t-miliya612:20180510204928p:plain

が、、、全体的に上手くいきませんでした。
SwipeViewsはデザインが違い、その他はそもそもビルドが出来ませんでした。
AndroidStudioのバージョンにもよるのでしょうが、別途自分で書いてみます。

SettingsActivity

f:id:t-miliya612:20180510205219p:plainf:id:t-miliya612:20180510205225p:plain

特徴

アプリの設定画面を生成してくれます。
アプリ内で完結したいものは、OSの設定画面へと遷移してくれます。

Source

AppCompatPreferenceAcrivityでPreferenceごとにFragmentを定義しています。

@TargetApi(Build.VERSION_CODES.HONEYCOMB)
class GeneralPreferenceFragment : PreferenceFragment() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        addPreferencesFromResource(R.xml.pref_general)
        setHasOptionsMenu(true)

        // Bind the summaries of EditText/List/Dialog/Ringtone preferences
        // to their values. When their values change, their summaries are
        // updated to reflect the new value, per the Android Design
        // guidelines.
        bindPreferenceSummaryToValue(findPreference("example_text"))
        bindPreferenceSummaryToValue(findPreference("example_list"))
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        val id = item.itemId
        if (id == android.R.id.home) {
            startActivity(Intent(activity, SettingsActivity::class.java))
            return true
        }
        return super.onOptionsItemSelected(item)
    }
}

の繰り返しです。

また、xml/pref_xxxでpreferenceごとの画面を定義しています。

<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <!-- NOTE: Hide buttons to simplify the UI. Users can touch outside the dialog to
         dismiss it. -->
    <!-- NOTE: ListPreference's summary should be set to its value by the activity code. -->
    <ListPreference
        android:defaultValue="180"
        android:entries="@array/pref_sync_frequency_titles"
        android:entryValues="@array/pref_sync_frequency_values"
        android:key="sync_frequency"
        android:negativeButtonText="@null"
        android:positiveButtonText="@null"
        android:title="@string/pref_title_sync_frequency" />

    <!-- This preference simply launches an intent when selected. Use this UI sparingly, per
         design guidelines. -->
    <Preference android:title="@string/pref_title_system_sync_settings">
        <intent android:action="android.settings.SYNC_SETTINGS" />
    </Preference>

</PreferenceScreen>

こうやって使うんですね。

ScrollingActivity

f:id:t-miliya612:20180510205238p:plain

特徴

  • CollapsingToolbarLayoutの中に配置したAppBarの大きさが可変になる

Source

そのままじゃ上手く表示されなかったのでちょっと修正……

以前紹介した本を参考にしました。

t-miliya612.hatenablog.com

xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.design.widget.CoordinatorLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <android.support.design.widget.AppBarLayout
        android:id="@+id/appbar"
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:elevation="10dp">

        <android.support.design.widget.CollapsingToolbarLayout
            android:id="@+id/toolbarLayout"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:layout_scrollFlags="scroll|exitUntilCollapsed">

            <android.support.v7.widget.Toolbar
                android:id="@+id/toolbar"
                android:layout_width="match_parent"
                android:layout_height="?attr/actionBarSize"
                android:background="@color/colorPrimary"
                app:layout_collapseMode="pin" />

        </android.support.design.widget.CollapsingToolbarLayout>
    </android.support.design.widget.AppBarLayout>

    <android.support.v4.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".ScrollingActivity"
        app:layout_behavior="@string/appbar_scrolling_view_behavior">

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_mar[f:id:t-miliya612:20180510205238p:plain]gin="@dimen/text_margin"
            android:text="@string/large_text" />

    </android.support.v4.widget.NestedScrollView>
</android.support.design.widget.CoordinatorLayout>

app:layout_behavior="@string/appbar_scrolling_view_behavior"を忘れるとコンテントの文字が被ってしまうので要注意です。

NavigationDrawer

f:id:t-miliya612:20180510205432p:plain

特徴

  • 左側から出てくるタイプのナビゲーション

Source

設定は結構簡単でした。

Activity

val toggle = ActionBarDrawerToggle(
        this, drawer_layout, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close)
drawer_layout.addDrawerListener(toggle)
toggle.syncState()

nav_view.setNavigationItemSelectedListener(this)

activity_navigation_drawer.xml

...

    <android.support.design.widget.NavigationView
        android:id="@+id/nav_view"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="start"
        android:fitsSystemWindows="true"
        app:headerLayout="@layout/nav_header_navigation_drawer"
        app:menu="@menu/activity_navigation_drawer_drawer" />

...

layout/nav_header_navigation_drawer.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="@dimen/nav_header_height"
    android:background="@drawable/side_nav_bar"
    android:gravity="bottom"
    android:orientation="vertical"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:theme="@style/ThemeOverlay.AppCompat.Dark">

    <ImageView
        android:id="@+id/imageView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:contentDescription="@string/nav_header_desc"
        android:paddingTop="@dimen/nav_header_vertical_spacing"
        app:srcCompat="@mipmap/ic_launcher_round" />

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="@dimen/nav_header_vertical_spacing"
        android:text="@string/nav_header_title"
        android:textAppearance="@style/TextAppearance.AppCompat.Body1" />

    <TextView
        android:id="@+id/textView"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/nav_header_subtitle" />

</LinearLayout>

menu/activity_navigation_drawer_drawer.xml

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    tools:showIn="navigation_view">

    <group android:checkableBehavior="single">
        <item
            android:id="@+id/nav_camera"
            android:icon="@drawable/ic_menu_camera"
            android:title="Import" />
        <item
            android:id="@+id/nav_gallery"
            android:icon="@drawable/ic_menu_gallery"
            android:title="Gallery" />
        <item
            android:id="@+id/nav_slideshow"
            android:icon="@drawable/ic_menu_slideshow"
            android:title="Slideshow" />
        <item
            android:id="@+id/nav_manage"
            android:icon="@drawable/ic_menu_manage"
            android:title="Tools" />
    </group>

    <item android:title="Communicate">
        <menu>
            <item
                android:id="@+id/nav_share"
                android:icon="@drawable/ic_menu_share"
                android:title="Share" />
            <item
                android:id="@+id/nav_send"
                android:icon="@drawable/ic_menu_send"
                android:title="Send" />
        </menu>
    </item>

</menu>

まとめ

全体的に学習材料として面白かったです。ただ、Tab系のActivityは謎でした……結構使いそうな気がするんだけどなぁ。
初めて実装する際には、手直しのことも考えるとEmptyActivityで頑張った方がよさそうですね。

そして、 初めから全部で2記事って決めると大変つらいことになる という学びを得ました。以上です。

Android StudioのActivityTemplate全部試してみた!(1/2)

AndroidStudioでActivityを追加するとき、とりあえず何も考えずにEmptyActivityを選択していました。

f:id:t-miliya612:20180506150334p:plain

これ他の選ぶとどうなってるんだろう、とふと気になってしまい、、、
とりあえず全部遊んでみることにしました。

リポジトリはこちら

github.com

目次(1/2)

Basic Activity

f:id:t-miliya612:20180506150519p:plain

特徴

  • Toolbarが設定されている
  • FloatActionButtonが配置されている
    • onClickListenerでToastを表示
  • content_{ActivityName}の名前で空のConstraintLayoutがincludeされている

特に難しいものはないですね。ToolbarやFABを生成してくれるのは使い勝手が良さそうです。

BottomNavigationActivity

f:id:t-miliya612:20180506150751p:plain

  • BottomNavigationの配置
    • 遷移するとtextが変更
    • 選択したアイコンは色が変わってサイズが少し大きくなる
  • res/menuを使用したデータ受け渡し (あれ、menuなんて使ったことなかった……これめっちゃ便利……)

FullscreenActivity

f:id:t-miliya612:20180506150804p:plain
Fullscreen表示
f:id:t-miliya612:20180506150809p:plain
Fullscreen表示解除時

  • Fullscreen表示に対応
  • タップしたらFullscreenの解除、設定のトグル

面白そうなのでソースも追っていきます。分解して見ていきましょう

Source

  • onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    setContentView(R.layout.activity_fullscreen)
    supportActionBar?.setDisplayHomeAsUpEnabled(true)

    mVisible = true

    // Set up the user interaction to manually show or hide the system UI.
    fullscreen_content.setOnClickListener { toggle() }

    // Upon interacting with UI controls, delay any scheduled hide()
    // operations to prevent the jarring behavior of controls going away
    // while interacting with the UI.
    dummy_button.setOnTouchListener(mDelayHideTouchListener)
}
  • toggle()
private fun toggle() {
    if (mVisible) {
        hide()
    } else {
        show()
    }
}
  • hide()
private fun hide() {
    // Hide UI first
    supportActionBar?.hide()
    fullscreen_content_controls.visibility = View.GONE
    mVisible = false

    // Schedule a runnable to remove the status and navigation bar after a delay
    mHideHandler.removeCallbacks(mShowPart2Runnable)
    mHideHandler.postDelayed(mHidePart2Runnable, UI_ANIMATION_DELAY.toLong())
}
  • mShowPart2Runnable
private val mShowPart2Runnable = Runnable {
    // Delayed display of UI elements
    supportActionBar?.show()
    fullscreen_content_controls.visibility = View.VISIBLE
}
  • mHidePart2Runnable
private val mHidePart2Runnable = Runnable {
    // Delayed removal of status and navigation bar

    // Note that some of these constants are new as of API 16 (Jelly Bean)
    // and API 19 (KitKat). It is safe to use them, as they are inlined
    // at compile-time and do nothing on earlier devices.
    fullscreen_content.systemUiVisibility =
            View.SYSTEM_UI_FLAG_LOW_PROFILE or
            View.SYSTEM_UI_FLAG_FULLSCREEN or
            View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
            View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
            View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or
            View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
}

systemUiVisibilityについては、こちらの記事が詳しいです。Android4.xの時期、ハードの物理ボタンが無くなった時期に生まれたんですね。Xperia acroとか懐かしい……。

y-anz-m.blogspot.jp

GoogleAdMobAdsActivity

f:id:t-miliya612:20180506151209p:plain
Interstitialタイプ
f:id:t-miliya612:20180506151217p:plain
Bannerタイプ

特徴

  • GoogleのAdMobを使用する
  • Interstitial(全画面)タイプの広告、Bannerタイプの広告の2種類がある

Source

Interstitialの場合もBannerの場合も、呼んでるAPIは変わらないみたいです。以下、Interstitialの例です。

build.gradle

dependencies {
    implementation 'com.google.android.gms:play-services-ads:15.0.1'

Activity

// Create the InterstitialAd and set the adUnitId (defined in values/strings.xml).
interstitialAd = newInterstitialAd()
loadInterstitial()

...

private fun newInterstitialAd(): InterstitialAd {
    return InterstitialAd(this).apply {
        adUnitId = getString(R.string.interstitial_ad_unit_id)
        adListener = object : AdListener() {
            override fun onAdLoaded() {
                next_level_button.isEnabled = true
            }

            override fun onAdFailedToLoad(errorCode: Int) {
                next_level_button.isEnabled = true
            }

            override fun onAdClosed() {
                // Proceed to the next level.
                goToNextLevel()
            }
        }
    }
}

private fun showInterstitial() {
    // Show the ad if it's ready. Otherwise toast and reload the ad.
    if (interstitialAd?.isLoaded == true) {
        interstitialAd?.show()
    } else {
        Toast.makeText(this, "Ad did not load", Toast.LENGTH_SHORT).show()
        goToNextLevel()
    }
}

private fun loadInterstitial() {
    // Disable the next level button and load the ad.
    next_level_button.isEnabled = false
    val adRequest = AdRequest.Builder()
            .setRequestAgent("android_studio:ad_template")
            .build()
    interstitialAd?.loadAd(adRequest)
}
...

基本的な流れとしては、

  • InterstitialAd(this).applyでInterstitialの広告を生成
  • AdRequest.Builder()で広告リクエストを作成
  • interstitialAd?.loadAd(adRequest)で実際に広告をリクエス

となっているようです。

GoogleMapsActivity

f:id:t-miliya612:20180506151222p:plain

特徴

  • GoogleMapを表示
  • 事前にgoogle developer consoleでGoogle MapsAPI keyの取得が必須
    • res/values/google_maps_api.xmlを参照

Source

コードはそんなに複雑ではありません。

build.gradle

dependencies {
    implementation 'com.google.android.gms:play-services-maps:15.0.1'

Activity

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

import com.google.android.gms.maps.CameraUpdateFactory
import com.google.android.gms.maps.GoogleMap
import com.google.android.gms.maps.OnMapReadyCallback
import com.google.android.gms.maps.SupportMapFragment
import com.google.android.gms.maps.model.LatLng
import com.google.android.gms.maps.model.MarkerOptions

class MapsActivity : AppCompatActivity(), OnMapReadyCallback {

    private lateinit var mMap: GoogleMap

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_maps)
        // Obtain the SupportMapFragment and get notified when the map is ready to be used.
        val mapFragment = supportFragmentManager
                .findFragmentById(R.id.map) as SupportMapFragment
        mapFragment.getMapAsync(this)
    }

    /**
     * Manipulates the map once available.
     * This callback is triggered when the map is ready to be used.
     * This is where we can add markers or lines, add listeners or move the camera. In this case,
     * we just add a marker near Sydney, Australia.
     * If Google Play services is not installed on the device, the user will be prompted to install
     * it inside the SupportMapFragment. This method will only be triggered once the user has
     * installed Google Play services and returned to the app.
     */
    override fun onMapReady(googleMap: GoogleMap) {
        mMap = googleMap

        // Add a marker in Sydney and move the camera
        val sydney = LatLng(-34.0, 151.0)
        mMap.addMarker(MarkerOptions().position(sydney).title("Marker in Sydney"))
        mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney))
    }
}

一旦今回はここまで!
残り6つはまた次回。。。

kotlin-stdlib-jreがdeprecatedになっていたので深掘りしてみた

環境

  • AndroidStudio 3.1.2
  • Kotlin 1.2.41

起こったこと

AndroidStudioで新しくプロジェクトを作ってみたら、{project}/app.gradleorg.jetbrains.kotlin:kotlin-stdlib-jre7がdeprecatedになっていました。

kotlin-stdlib-jre7 is deprecated since 1.2.0 and should be replaced with kotlin-stdlib-jdk7

f:id:t-miliya612:20180502173641p:plain

言われた通りjre-7からjdk-7に変えたら直りました。よかったです。

……だけで終わるのもアレなので、しっかり調べてみました。

kotlin-stdlibについて

まさにStandardなlibraryで、Kotlinのコア部分の実装を担っています。

The Kotlin Standard Library provides living essentials for everyday work with Kotlin. These include:
- Higher-order functions implementing idiomatic patterns (let, apply, use, synchronized, etc).
- Extension functions providing querying operations for collections (eager) and sequences (lazy).
- Various utilities for working with strings and char sequences.
- Extensions for JDK classes making it convenient to work with files, IO, and threading.

kotlin-stdlib - Kotlin Programming Language

kotlin-stdlibはコア部分、kotlin-stdlib-jre/jdkはextension的な部分っぽいですね。
似たような議論がこちらに。

discuss.kotlinlang.org

変更理由

Java9、特にその中でもモジュールシステムのサポートのためみたいです。

The Kotlin standard library is now fully compatible with the Java 9 module system, which forbids split packages (multiple jar files declaring classes in the same package). In order to support that, new artifacts kotlin-stdlib-jdk7 and kotlin-stdlib-jdk8 are introduced, which replace the old kotlin-stdlib-jre7 and kotlin-stdlib-jre8.

The declarations in the new artifacts are visible under the same package names from the Kotlin point of view, but have different package names for Java. Therefore, switching to the new artifacts will not require any changes to your source code.

What's New in Kotlin 1.2 - Kotlin Programming Language

モジュールシステムについて

  • 別名 Project Jigsaw
  • Packageの上位概念としてモジュールを作る

実際にKotlin内のコードでも使われていますね。

github.com

目的

  • 構成の信頼性の向上
    • モジュール間の依存性を明示的に宣言
    • コンパイル時、実行時に依存性の認識が可能
  • 強力なカプセル化
    • モジュールは明示的にエクスポートしない限り外部からのアクセス不可
    • エクスポートは全開放するか特定のモジュールにのみ解放するか選べる
  • スケーラブルなJavaプラットフォーム
    • プラットフォームがモジュールで分割される
    • 必要なものだけを集めたカスタムランタイムを作れる
      • ランタイムのサイズを削減できる
  • プラットフォームの整合性向上
    • 内部APIを完全にカプセル化できる
    • プラットフォーム内でアプリに使われたくないAPIを隠蔽できる
  • パフォーマンスの向上
    • これちょっとわかりませんでした。。。

http://www.oracle.com/webfolder/technetwork/jp/javamagazine/Java-SO17-Modules.pdf

エクスポートを明示的にできるのは良さそうですね!openが必要なのはKotlinの影響もあるんでしょうか。

所感

Android-Java-Kotlinと変数が増えると最新版を追っかけるのは大変ですね……。
モジュールシステムに関しては、gradleを使ったモジュールとの今後が気になります。試しにこれ使ってマルチモジュールのアプリ書いてみたいなぁ(伏線)
また、stdlibに関して、ネーミングの理由はよくわからないままでした。何故拡張版をjreなんてネーミングにしたんでしょう。

おまけ

AndroidのJava7サポートって完全じゃなかったようなーと思い調べてみた所、こんな記事が見つかりました。

tech.speee.jp

Androidの開発環境における「Javaのバージョン」はいくつかの考え方があり、自明ではありません。ここでは3つに分けて考えます。

ひとつはAndroid端末にインストールされているAndroid OS、それにバンドルされているDalvik処理系(仮想マシン)がどのJVMバージョンに相当するかというものです。

もうひとつは、Android端末にインストールされているAndroid OS、それにバンドルされているJava標準ライブラリのバージョンです。

最後に、Androidアプリケーションを開発する際にJavaコンパイラに与えるバージョンです。

あれ、そしたら結局AndroidってどのJava使えるの……?

Java 8 言語機能の使用  |  Android Developers

Android Studio では、特定の Java 8 言語機能とそれらを使用するサードパーティ ライブラリのビルトイン サポートを提供しています。

し、知らなかった……。
こちらからは以上です。

公式のcodelabでFlutterに入門してみた

今度Flutter Meetup Tokyo #2に参加するので、事前にちょっと手を動かしてみました。

flutter-jp.connpass.com

What’s Flutter

Flutter - Beautiful native apps in record time

Googleが開発しているmobile appのSDKで、Dartという言語を用いて開発します。
本家サイトによると、特徴は以下の5つ。

  • Fast development
    • ホットリロードが可能
  • Expressive and Flexible UI
  • Modern, reactive framework
  • Access native features and SDKs
    • プラットフォーム(OS)のAPIや3rd partyのSDK、ネイティブのコードを呼び出せる(素敵)
  • Unified app development
    • AndroidiOSも、プラットフォームの差を意識することなく開発可能
    • Mobile Appの開発経験がなくても大丈夫!
    • 開発、テスト、デバッグコンパイル、配布に至るまでサポートするツールが充実している

環境構築

環境

1. install Dart

$ brew tap dart-lang/dart

==> Tapping dart-lang/dart
Cloning into '/usr/local/Homebrew/Library/Taps/dart-lang/homebrew-dart'...
remote: Counting objects: 5, done.
remote: Compressing objects: 100% (5/5), done.
remote: Total 5 (delta 0), reused 2 (delta 0), pack-reused 0
Unpacking objects: 100% (5/5), done.
Tapped 1 formula (30 files, 26.3KB)

$ brew install dart --with-dartium --with-content-shell

==> Installing dart from dart-lang/dart
==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/sdk/dartsdk-macos-x64-release.zip
######################################################################## 100.0%
==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/dartium/dartium-macos-x64-release.zip
######################################################################## 100.0%
==> Downloading https://storage.googleapis.com/dart-archive/channels/stable/release/1.24.3/dartium/content_shell-macos-x64-relea
######################################################################## 100.0%
==> Caveats
Please note the path to the Dart SDK:
  /usr/local/opt/dart/libexec
==> Summary
🍺  /usr/local/Cellar/dart/1.24.3: 3,039 files, 657.7MB, built in 21 seconds

2. install Flutter

$ git clone https://github.com/flutter/flutter.git

Cloning into 'flutter'...
remote: Counting objects: 120517, done.
remote: Total 120517 (delta 0), reused 0 (delta 0), pack-reused 120516
Receiving objects: 100% (120517/120517), 36.60 MiB | 3.11 MiB/s, done.
Resolving deltas: 100% (90418/90418), done.

$ git co v0.3.1 # flutter公式ページのzip最新版がv0.3.1だったため(2018/04/29現在)
$ vim ~/.zprofile

export PATH=$HOME/flutter/bin:$PATH 

3. Set up environment

$ flutter doctor

Downloading Dart SDK from Flutter engine 09d05a38912a3c1a906e95099cac9a7e14fae85f...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 43.9M  100 43.9M    0     0  9507k      0  0:00:04  0:00:04 --:--:-- 9508k
Building flutter tool...

  ╔════════════════════════════════════════════════════════════════════════════╗
  ║                 Welcome to Flutter! - https://flutter.io                   ║
  ║                                                                            ║
  ║ The Flutter tool anonymously reports feature usage statistics and crash    ║
  ║ reports to Google in order to help Google contribute improvements to       ║
  ║ Flutter over time.                                                         ║
  ║                                                                            ║
  ║ Read about data we send with crash reports:                                ║
  ║ https://github.com/flutter/flutter/wiki/Flutter-CLI-crash-reporting        ║
  ║                                                                            ║
  ║ See Google's privacy policy:                                               ║
  ║ https://www.google.com/intl/en/policies/privacy/                           ║
  ║                                                                            ║
  ║ Use "flutter config --no-analytics" to disable analytics and crash         ║
  ║ reporting.                                                                 ║
  ╚════════════════════════════════════════════════════════════════════════════╝
  
Downloading Material fonts...                                0.9s
Downloading package sky_engine...                            0.5s
Downloading common tools...                                  1.1s
Downloading darwin-x64 tools...                              1.9s
Downloading android-arm-profile/darwin-x64 tools...          0.7s
Downloading android-arm-release/darwin-x64 tools...          0.8s
Downloading android-arm64-profile/darwin-x64 tools...        0.8s
Downloading android-arm64-release/darwin-x64 tools...        0.8s
Downloading android-x86 tools...                             1.8s
Downloading android-x64 tools...                             1.5s
Downloading android-arm tools...                             1.6s
Downloading android-arm-profile tools...                     1.0s
Downloading android-arm-release tools...                     1.2s
Downloading android-arm64 tools...                           1.3s
Downloading android-arm64-profile tools...                   1.1s
Downloading android-arm64-release tools...                   1.0s
Downloading ios tools...                                     2.2s
Downloading ios-profile tools...                             2.1s
Downloading ios-release tools...                             1.9s
Downloading Gradle Wrapper...                                0.2s
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel unknown, v0.3.1, on Mac OS X 10.13.4 17E199, locale ja-JP)
-Error executing simctl: -6
dyld: Symbol not found: _SimDeviceBootKeyDisabledJobs
  Referenced from: /Applications/Xcode.app/Contents/Developer/usr/bin/simctl
  Expected in: /Library/Developer/PrivateFrameworks/CoreSimulator.framework/Versions/A/CoreSimulator
 in /Applications/Xcode.app/Contents/Developer/usr/bin/simctl

[!] Android toolchain - develop for Android devices (Android SDK 27.0.3)
    ! Some Android licenses not accepted.  To resolve this, run: flutter doctor --android-licenses
[!] iOS toolchain - develop for iOS devices (Xcode 9.3)
    ✗ Xcode requires additional components to be installed in order to run.
      Launch Xcode and install additional required components when prompted.
    ✗ libimobiledevice and ideviceinstaller are not installed. To install, run:
        brew install --HEAD libimobiledevice
        brew install ideviceinstaller
    ✗ ios-deploy not installed. To install:
        brew install ios-deploy
    ✗ CocoaPods not installed.
        CocoaPods is used to retrieve the iOS platform side's plugin code that responds to your plugin usage on the Dart side.
        Without resolving iOS dependencies with CocoaPods, plugins will not work on iOS.
        For more info, see https://flutter.io/platform-plugins
      To install:
        brew install cocoapods
        pod setup
[✓] Android Studio (version 3.1)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[!] IntelliJ IDEA Community Edition (version 2017.2.6)
    ✗ Flutter plugin not installed; this adds Flutter specific functionality.
    ✗ Dart plugin not installed; this adds Dart specific functionality.
[✓] VS Code (version 1.22.2)
[!] Connected devices
    ! No devices available

! Doctor found issues in 4 categories.

いくつか引っかかりました。

Android license

  • Some Android licenses not accepted. To resolve this, run: flutter doctor --android-licenses

$ flutter doctor --android-licenses

3 of 6 SDK package licenses not accepted. 100% Computing updates...             
Review licenses that have not been accepted (y/N)? y

1/3: License android-googletv-license:
2/3: License google-gdk-license:
3/3: License mips-android-sysimage-license:

iOS

  • Xcodeのcomponents
  • gem
    • libimobiledevice
    • ideviceinstaller
    • ios-deploy
    • CocoaPods

CocoaPods入れてなかったのがつらい。。。30分休み。

Android Studio

  • Flutter plugin
  • Dart plugin

Device

  • Device繋がってないよ!はい。

対応し、再挑戦してみます。

$ flutter doctor

master
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel unknown, v0.3.1, on Mac OS X 10.13.4 17E199, locale ja-JP)
[✓] Android toolchain - develop for Android devices (Android SDK 27.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 9.3)
[✓] Android Studio (version 3.1)
[✓] VS Code (version 1.22.2)
[✓] Connected devices (2 available)

いい感じです。これで開発環境が整いました。

codelabについて

flutter.io

スタートアップの会社名候補を生成するシンプルなモバイルアプリを作っていきます。お気に入り機能があり、別のページでお気に入りの名前リストを表示することもできます。
f:id:t-miliya612:20180429233102p:plain

開発はAndroidStudioを使いましたが、VScodeなどを使っても良いそうです。

Step 1: Create the starting Flutter app

Flutterのプロジェクトを新規生成します。
Android Studioを使う場合はウィザードから、VScodeを使う場合はコマンドからアプリの雛形を生成できます。

f:id:t-miliya612:20180429124315p:plain
f:id:t-miliya612:20180429124335p:plain
f:id:t-miliya612:20180429124343p:plain
f:id:t-miliya612:20180429124355p:plain

ファイル構成はこんな感じです。

├── README.md
├── android # androidネイティブのコード
├── build
├── ios # iOSネイティブのコード
├── lib
│   └── main.dart # 手を加えていくファイル
├── pubspec.lock
├── pubspec.yaml # 依存関係などなどを管理するファイル
└── test
    └── widget_test.dart

終わったら、codelabにあるコードでlib/main.dartを置き換えましょう。

Step 2: Use an external package

english_wordsというプラグインを入れます。プラグインの追加は、以下の手順で行います。

  1. pubspec.yamlに追記
    • english_words: ^3.1.0
  2. Android Studioのタブに表示されているPackages Getをクリック f:id:t-miliya612:20180429124552p:plain
    • 以下が表示されればOK
${PATH_TO_FLUTTER}/flutter/bin/flutter --no-color packages get
Running "flutter packages get" in ${YOUR_APP_NAME}...
Process finished with exit code 0
  1. lib/main.dartにimportを追記
    • import 'package:english_words/english_words.dart';

後はいつも通り使うだけです。特に難しいところはなさそうです。

Step 3: Add a Stateful widget

WidgetにはStatefulとStatelessの2種類があります。画面を表示している間にWidgetを書き換えるかどうかで使い分けます。今回のアプリでは無限スクロールのListを描画するので、StatefulWidgetを拡張したクラスを使用します。
まずは中央に表示されたtextを試しにstatefulにしてみます。

import 'package:flutter/material.dart';
import 'package:english_words/english_words.dart';

void main() => runApp(new MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Welcome to Flutter',
      home: new Scaffold(
        appBar: new AppBar(
          title: new Text('Welcome to Flutter'),
        ),
        body: new Center(
          child: new RandomWords(),
        ),
      ),
    );
  }
}

class RandomWords extends StatefulWidget {
  @override
  createState() => new RandomWordsState();
}

class RandomWordsState extends State<RandomWords> {
  @override
  Widget build(BuildContext context) {
    final wordPair = new WordPair.random();
    return new Text(wordPair.asPascalCase);
  }
}

Step 4: Create an infinite scrolling ListView

いよいよ無限スクロールのListViewを生成します。ListView.builder()を返すWidgetを定義し、builderの引数として渡すitemBuilderプロパティの中でリスト生成のロジックを記述します。

(省略...)
class RandomWordsState extends State<RandomWords> {
  final _suggestions = <WordPair>[];

  final _biggerFont = const TextStyle(fontSize: 18.0);
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Startup Name Generator'),
      ),
      body: _buildSuggestions(),
    );
  }
  Widget _buildSuggestions() {
    return new ListView.builder( // ListView.builderを生成
      padding: const EdgeInsets.all(16.0),
      itemBuilder: (context, i){ // itemBuilderのプロパティにリストを描画する無名関数を定義
        if (i.isOdd) return new Divider();
        final index = i ~/ 2;
        if (index >= _suggestions.length) {
          _suggestions.addAll(generateWordPairs().take(10));
        }
        return _buildRow(_suggestions[index]);
      },
    );
  }

  // リスト1行をセットする
  Widget _buildRow(WordPair pair) {
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
    );
  }
}

Step 5: Add interactivity

ハート型アイコンをセットして、タップするとお気に入り登録できるようにします。Iconsクラスがすごく充実していて、アイコン追加がかなり楽そうです。

docs.flutter.io

Androidで言うonClickメソッドは、onTapプロパティに対応します。

(省略...)
  Widget _buildRow(WordPair pair) {
    final alreadySaved = _saved.contains(pair);
    return new ListTile(
      title: new Text(
        pair.asPascalCase,
        style: _biggerFont,
      ),
      trailing: new Icon(
        alreadySaved ? Icons.favorite : Icons.favorite_border,
        color: alreadySaved ? Colors.red : null,
      ),
      onTap: () {
        setState(() {
          if (alreadySaved) {
            _saved.remove(pair);
          } else {
            _saved.add(pair);
          }
        });
      },
    );
  }

Step 6: Navigate to a new screen

画面遷移のコードを書いていきます。Flutterでは新しい画面のことをrouteと言うみたいですね。
routeのスタックは、Navigationクラスを使って管理していきます。

(省略...)
  void _pushSaved(){
    Navigator.of(context).push(
      new MaterialPageRoute(
          builder: (context) {
            // お気に入り登録されたリストを各行にマップ
            final tiles = _saved.map( 
                (pair) {
                  return new ListTile(
                    title: new Text(
                      pair.asPascalCase,
                      style: _biggerFont,
                    ),
                  );
                },
            );
            // リストを生成
            final divided = ListTile
              .divideTiles(
                  context: context,
                  tiles: tiles,
              )
              .toList();
            // appBarと画面のコンテンツ(リスト)をセット
            return new Scaffold(
              appBar: new AppBar(
                title: new Text('Saved Suggestions'),
              ),
              body: new ListView(children: divided),
            );
          },
      ),
    );
  }

Step 7: Change the UI using Themes

UIの色をいじってみます。今回はひとまず、primaryColorを変更してappBarの色を変えてみます。

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Startup Name Generator',
      theme: new ThemeData(
        primaryColor: Colors.white,
      ),
      home: new RandomWords(),
    );
  }
}

簡単ですね!ただ、結構大胆にカスタマイズをしようとすると大変なことになりそうな。。。
MaterialDesignからどうしても離れなきゃいけないときは厳しそうですね。

作ったもの

github.com

f:id:t-miliya612:20180429233141p:plain
f:id:t-miliya612:20180429233147p:plain

まとめ

一通りアプリを完成させるところまでやってみました。かなり簡単にそれなりのUIを作成することができました。
イメージとしてはAnkoのような感じかなーと思っていたのですが、ちょっと違う印象でした。ReactやVueなどのライブラリは使ったことがないのですが、そういったjsのライブラリで培ったノウハウが必要になりそうです。

とはいえ、これでFlutterのことが何となく完全に理解できました。今からmeetupが楽しみです。

今後やりたいこと

  • ファイル構成を考える
    • ファイルってどうやって分けていくんだろ
    • いい加減Reactやらねば。。。
  • OS依存の機能を使う
    • network通信
    • GPS
    • camera
    • SMS(出来るかな……?)

#技術書典 に行ってきたからまずは買ったものをまとめておく

昨日、技術書典4に行ってきました。前回に引き続き2回目の参加です。
今回は晴れだったこともあり、恐ろしいほどの参加者数に。

twitter.com

だいぶ気合いの入った入場待ちの列に感化されて、合計12冊の収穫を得ました。熱かったししょうがないです。
もちろんまだ読み終わってないのですが、一旦出会い等をまとめておきます。

目次

  • DNSをはじめよう ~基礎からトラブルシューティングまで~
  • Now and Future
  • SVGで作ろうローディングアニメーション
  • こうしてぼくらは、書籍を売るアプリを作った
  • こうしてぼくらは、書籍を売るアプリを作った2.0.1
  • Gopherの休日
  • 大きめのAndroidアプリでの設計を考えてみる〜pocket〜
  • Excel働き方改革(仮)
  • Vue.jsとFirebaseで作るミニWEBサービス 初めてのサーバーレスシングルアプリケーション
  • ざっクリわかる Ubuntu 18.04 LTS
  • Nature Design with Houdini 自然界のアルゴリズムを利用したデザインレシピ集
  • Android Things with Kotlin Hands-on Book

DNSをはじめよう ~基礎からトラブルシューティングまで~

techbookfest.org

気になった理由

  • 学生時代、wordpress独自ドメインを当てるときまさに「数時間かかるんだよねー」とか知ったかぶりをしてた
  • 会社入れば研修とかでやるじゃろ、って思ってたらあんまやらなかった
  • 今後自分でサービスを立ち上げたりするときにアワアワしたくない
  • ドリル付き←最高
  • 半透明の遊び紙で製本してあり、本自体のクオリティが高い

Now and Future

techbookfest.org

気になった理由

第1章 Android通知最前線 What's new in Android P

  • 普段Androidの開発をする上で、新しいOSの機能ってシェアの都合上すぐに触ることがない(アプリに求められるのはある程度色々なデバイスで動くこと)
  • 何だかんだ理由をつけて調べてなかったが、Twitterで情報収集するようにしてから気になって仕方がない

第2章 Fit API for Androidを使ってみよう

  • 筋肉になりたい
  • 最近乗ってないけどロードバイク持ってる
  • Garmin高いから自分でやっていきたい

第7章 Androidライブラリ実践入門

  • Androidライブラリ、業務でやる必要があった
  • 断片的な知識しかなかったのでまとめて頭に入れたかった
  • OSS公開、貢献に上手く繋げていきたい

SVGで作ろうローディングアニメーション

techbookfest.org

気になった理由

  • 俺たちは雰囲気でSVGをやっている
  • 学生時代にイラレで書き出しとかやってたけど、中身よく分かってない
  • 目的は多画面サイズ対応くらいだった

こうしてぼくらは、書籍を売るアプリを作った

techbookfest.org

気になった理由

  • 目当ての章
    • 第3章 今日から始めるChatOpsの勘どころ
    • 第4章 Debug Menuはじめました。
    • 第5章 実践Go言語のインタフェース
  • 特に第4章の学びが濃そう
    • debugメニュー、「絶対触っちゃいけないゾーン」になってしまったり
    • productionを想定してちゃんと管理しているのが流石

こうしてぼくらは、書籍を売るアプリを作った2.0.1

techbookfest.org

気になった理由

  • 目当ての章
    • 第2章 Google App EngineでGoのバージョンアップを行う
    • 第4章 キンパツは企画を考え、どんなことをしてきたのか
  • キンパツこと@operandosさんの話は特にきになる
  • ようやくキャリア周りの話に敏感になるようになってきた

Gopherの休日

techbookfest.org

気になった理由

  • お世話になっているgolang.tokyoの頒布物
  • 表紙が最高
  • @tenntennさんが売り子してるのすごい

大きめのAndroidアプリでの設計を考えてみる〜pocket〜

techbookfest.org

気になった理由

Excel働き方改革(仮)

techbookfest.org

気になっていること

  • 自動化自動化と叫ばれているだけあって、いわゆる神Excelはほとんど姿を見かけない(見かけたことがない)
  • ただ、たまに他の人のスプレッドシートを見ると「オッ」となることが
  • 我々は神Excelの悲劇を忘れてはならない
  • (正直何が出来て何がつらいのかよくわかってないので、雰囲気でしか喋れない)

Vue.jsとFirebaseで作るミニWEBサービス 初めてのサーバーレスシングルアプリケーション

techbookfest.org

作者様はこちら nabettu.hatenablog.com

気になった理由

  • SPA作ったことない!
  • Vue.jsもいい加減触りたい!
  • しっかりWebサービス立てるのも大事だけど、Firebase等使ってライトにサクッと立てる技術も大事だと思った

ざっクリわかる Ubuntu 18.04 LTS

techbookfest.org

気になった理由

  • これ学生時代に欲しかったなぁとの思いから
  • 大学の授業や先輩の宗教上の理由から、やたらとCentじゃなくてUbuntuを触る機会が多かった
  • やっぱり初めに触れるなら、CUIだけのサーバー用途よりもGUIを含めたデスクトップ環境の方が楽でいい
  • 「ざっクリ」というだけあってそんなに濃い内容が詰まってるわけではないけれど、聞かれたときにスッと出したい本

Nature Design with Houdini 自然界のアルゴリズムを利用したデザインレシピ集

techbookfest.org

気になった理由

  • これぞ技術書典に来た理由
  • webとかAndroidとか、普段の業務や趣味の領域じゃ出会わない分野と出くわすことができる
  • アルゴリズミックデザインの本
    • アルゴリズムを用いて形状のデザインをする」

    • 自分の手入力により形を作り上げていくデータが重くなりがちな作り方ではなく、数理的なルールを記述するだけで少ないデータで複雑な形状を作ることができる

  • SVGと繋がる点がありそう
  • 対象読者は、アルゴリズミックデザインについて全く知らない人、ではないので、ちょっとずつ勉強しながら読んでいきたい

Android Things with Kotlin Hands-on Book

techbookfest.org

気になった理由

  • 今のところAndroidアプリ開発者としてお仕事をしている
  • PWAなどが流行っている中、今やっているネイティブアプリの知識って今後どこまで通用するのかちょっと不安になる
  • この本のことを知ってからAndroid Thingsのことも知ったんだけど、こういった道で自分の経験を活かしていけるのであれば……という気持ち。

以上です! (長かった……)
GWに向けて、最高の遊び道具が揃った感じですね!やっていきましょう!

ちなみに

買ったものはTrelloでまとめてみるトライアルをしてみています。