AndroidJetpack概要 &旧AACの紹介

AndroidJetpack概要 &旧AACの紹介

Ac6f0faaab626a101cab97cab29f086f?s=128

katsuki-nakatani

June 14, 2018
Tweet

Transcript

  1. Android Jetpack概要 &旧Architecture Componentの紹介 Osaka Mix Leap Study #16 -

    Android Jetpack 勉強会
  2. Speaker 中谷 克紀 仕事  Enterprise Server Engineer GDG 神戸スタッフ Twitter  @KatsukiNakatani

  3. Version History https://en.wikipedia.org/wiki/Android_(operating_system)

  4. Androidの開発の難しさ ・Javaのバージョンが古く最新のJavaが使えない ・フラグメントによるOSバージョンの差異 ・Androidの特徴(ライフサイクル)を意識した実装が難しい

  5. 言語の改善 ・Javaのバージョンが古く最新のJavaが使えない Kotlinの登場により、Javaバージョンを気にせずコードを書ける環境になった

  6. バージョンフラグメントの改善/開発支援 Android Support Library , AppCompat Project Treble

  7. Androidの特徴(ライフサイクル)を意識した実装 ・EventBus ・RxJava ・Icepick

  8. 問題が。。。 ・OSSの更新が止まる ・最小バージョンの切り上げ ・オレオレFrameworkの誕生 ・神Activityの誕生

  9. 本当にしたいことって? アプリを作りたいことですよね? 自分のサービスや業務に関わるロジックを書きたいですよね?

  10. Android Jetpackの登場です https://developer.android.com/jetpack/

  11. 目的 https://developer.android.com/jetpack/ ・開発速度を上げましょう    各コンポーネントは、自由に組み合わせ可能で、 Kotlinを活用しながら連携するように作られています ・ボイラープレートを減らしましょう   BackgroundTask,Navigation,Lifecycleなどを管理してくれるライブラリがありますので   アプリの開発に集中できます ・アプリを高品質にしましょう   モダンなデザインプラクティスに基づいて後方互換性を保ちながらクラッシュを少なくし、メモリリークを少ないように

      コンポーネントは作られています
  12. Jetpackですが https://developer.android.com/jetpack/ ・今回のIOで全部がいきなり登場した機能ではないです ・いくつかの機能は、既存のものを再カテゴライズしたようなものとなっています

  13. コンポーネント一覧 https://developer.android.com/jetpack/

  14. パッケージ名の変更 SupportLibraryとともに、Architecture Componentは androidxのパッケージ名に変更されます。 本スライドでは従来のパッケージ名で記載しています https://developer.android.com/topic/libraries/architecture/adding-components https://developer.android.com/topic/libraries/support-library/refactor

  15. LiveData AndroidのLifecycleを考慮してくれてデータの購読・解除が行える

  16. LiveData ボタンを押す ネットワーク通信とか 時間のかかる処理 結果を取得 画面に反映

  17. 実装してみよう dependencies { def lifecycle_version = "1.1.1" // ViewModel and

    LiveData implementation "android.arch.lifecycle:extensions:$lifecycle_version" // alternatively - just ViewModel implementation "android.arch.lifecycle:viewmodel:$lifecycle_version" // use -ktx for Kotlin ※後述のViewModelを使うときに追加します // alternatively - just LiveData implementation "android.arch.lifecycle:livedata:$lifecycle_version" // alternatively - Lifecycles only (no ViewModel or LiveData). // Support library depends on this lightweight import implementation "android.arch.lifecycle:runtime:$lifecycle_version" annotationProcessor "android.arch.lifecycle:compiler:$lifecycle_version" // alternately - if using Java8, use the following instead of compiler implementation "android.arch.lifecycle:common-java8:$lifecycle_version" // optional - ReactiveStreams support for LiveData implementation "android.arch.lifecycle:reactivestreams:$lifecycle_version" // optional - Test helpers for LiveData testImplementation "android.arch.core:core-testing:$lifecycle_version" }
  18. LiveDataを使用する class MainActivity : AppCompatActivity() { val viewModel: MainViewModel =

    MainViewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main_activity) viewModel.counter.observe(this, Observer { findViewById<TextView>(R.id.text_hello).setText("counter :" + it) }) findViewById<View>(R.id.button).setOnClickListener({ viewModel.countUp() }) } } MainActivity.kt class MainViewModel { val counter: MutableLiveData<Int> = MutableLiveData() init { counter.postValue(0) } fun countUp() { counter.postValue(counter.value?.plus(1)) } } MainViewModel.kt
  19. データバインディングを使用する場合 class MainActivity : AppCompatActivity() { val viewModel: MainViewModel =

    MainViewModel() lateinit var databinding:MainActivityBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) databinding = DataBindingUtil.setContentView(this,R.layout.main_activity) databinding.viewModel = viewModel databinding.setLifecycleOwner(this) databinding.button.setOnClickListener({ viewModel.countUp() }) } } MainActivity.kt class MainViewModel { var counterText:MutableLiveData<String> = MutableLiveData() var counter = 0 init { counter.postValue(0) } fun countUp() { counterText.postValue("count" + counter) counter++ } } MainViewModel.kt android.databinding.enableV2=true gradle.properties <Button android:id="@+id/button" android:layout_width="wrap_content" android:text="@{viewModel.counterText}" android:layout_height="wrap_content" /> main_activity.xml
  20. RecyclerViewの利用 class SampleListAdapter(internal val context: Context) :                 ListAdapter<String, SampleListAdapter.ViewHolder>(SampleListAdapter.DIFF_CALLBACK)

    { private var inflater: LayoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { var binding: ViewDataBinding = DataBindingUtil.bind(itemView)!! } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = inflater.inflate(R.layout.sample_list, parent, false) return ViewHolder(view) } override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.binding.setVariable(BR.text, getItem(position)) } companion object { val DIFF_CALLBACK = object : DiffUtil.ItemCallback<String>() { override fun areItemsTheSame(old: String, new: String): Boolean { return TextUtils.equals(old,new) } override fun areContentsTheSame(old: String, new: String): Boolean { return TextUtils.equals(old,new) } } } } SampleAdapter.kt lateinit var sampleListAdapter: SampleListAdapter override fun onCreate(savedInstanceState: Bundle?) { sampleListAdapter = SampleListAdapter(this) databinding.recyclerView.layoutManager = LinearLayoutManager(this) databinding.recyclerView.adapter = sampleListAdapter viewModel.sampleList.observe(this, Observer { sampleListAdapter.submitList(it) }) findViewById<View>(R.id.button).setOnClickListener({ viewModel.addList() }) } class MainViewModel { var counter: Int = 0 val sampleList: MutableLiveData<List<String>> = MutableLiveData() init { sampleList.postValue(arrayListOf()) } fun addList() { sampleList.postValue( IntProgression.fromClosedRange(0,counter,1).map { it -> "text" + it }.toList()) counter++ } } SampleActivity.kt MainViewModel.kt
  21. ここで問題が発生します Activityの生存期間 ViewModelの生存期間(LiveData) 今の使い方だと、Activityは画面回転などで簡単に破棄されてしまいます そのため、LiveDataも破棄されたタイミングで消滅してしまいます

  22. これをネットワーク通信に置き換えると 画面を開く onCreateでViewModelを生成 通信中に画面回転が発生したりすると、 ViewModelごと破棄されるし、 通信も2回目が発生してしまう 状態を気にしなくて良い ViewModelがほしい 外部通信… 画面回転

    ViewModel/ Activity破棄
  23. これを解決するのが JetpackのViewModelです https://developer.android.com/topic/libraries/architecture/viewmodel

  24. ViewModelの実装 class MainViewModel : ViewModel() { MainViewModel.kt lateinit var viewModel:

    MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) MainActivity.kt
  25. ViewModelを利用する上での注意点 ・ViewModelでViewやContextを参照しない ・Activityより生存期間は長いが、低メモリ状態などで破棄されることはありうる。  破棄されたら困る情報は SaveInstanceで保存 https://developer.android.com/topic/libraries/architecture/saving-states ここにViewModel,SaveInstanceState,Persistent Storageの棲み分けがわかりやすく書いてあります

  26. DemoCode override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) database = Room.databaseBuilder(applicationContext,AppDatabase::class.java,"sample.db")

    .build() databinding = DataBindingUtil.setContentView(this, R.layout.main_activity) databinding.setLifecycleOwner(this) viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) databinding.viewModel = viewModel viewModel.find(database) userListAdapter = UserListAdapter(this) databinding.recyclerView.layoutManager = LinearLayoutManager(this) databinding.recyclerView.adapter = userListAdapter viewModel.userList.observe(this, Observer { Log.d("test","fire observe") userListAdapter.submitList(it) }) } class MainViewModel : ViewModel() { var userList: MutableLiveData<List<User>> = MutableLiveData() var progress = ObservableBoolean(false) fun find(appDatabase: AppDatabase) { if(userList.value == null) { Log.d("test", "findStart") progress.set(true) appDatabase.userDao().queryEnabledUser() .subscribeOn(Schedulers.computation()) .subscribe({ it -> Thread.sleep(3000) Log.d("test", "postValue") userList.postValue(it) progress.set(false) }) } } } MainViewModel.kt MainActivity.kt
  27. Demoログ 2018-06-14 10:01:44.591 29550-29550/com.gdgkobe.samplejetpack D/test: findStart 2018-06-14 10:01:44.597 29550-29550/com.gdgkobe.samplejetpack D/test:

    onStart 2018-06-14 10:01:47.679 29550-29645/com.gdgkobe.samplejetpack D/test: postValue 2018-06-14 10:01:47.679 29550-29550/com.gdgkobe.samplejetpack D/test: fire observe 2018-06-14 10:01:49.819 29550-29550/com.gdgkobe.samplejetpack D/test: onStop 2018-06-14 10:01:51.621 29550-29550/com.gdgkobe.samplejetpack D/test: findStart 2018-06-14 10:01:51.631 29550-29550/com.gdgkobe.samplejetpack D/test: onStart 2018-06-14 10:01:53.104 29550-29550/com.gdgkobe.samplejetpack D/test: onStop 2018-06-14 10:01:54.697 29550-29590/com.gdgkobe.samplejetpack D/test: postValue 2018-06-14 10:01:59.616 29550-29550/com.gdgkobe.samplejetpack D/test: onStart 2018-06-14 10:01:59.620 29550-29550/com.gdgkobe.samplejetpack D/test: fire observe
  28. Room ・SQLiteを簡単に扱えるライブラリ ・戻り値をLiveDataやRxJavaで返せる ・クエリ構築時にコンパイルエラーでエラーを検知できる

  29. build.gradle追加 dependencies { def room_version = "1.1.0" // or, for

    latest rc, use "1.1.1-rc1" implementation "android.arch.persistence.room:runtime:$room_version" annotationProcessor "android.arch.persistence.room:compiler:$room_version" // optional - RxJava support for Room implementation "android.arch.persistence.room:rxjava2:$room_version" // optional - Guava support for Room, including Optional and ListenableFuture implementation "android.arch.persistence.room:guava:$room_version" // Test helpers testImplementation "android.arch.persistence.room:testing:$room_version" } https://developer.android.com/topic/libraries/architecture/adding-components#room
  30. Entityクラスを作成 @Entity(tableName = "user") data class User( @PrimaryKey(autoGenerate = true)

    val id: Long, var firstName: String, var lastName: String, var enable: Boolean = false) { }
  31. Daoを作成 @Dao interface UserDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insertUsers(vararg

    users: User) @Update fun updateUsers(vararg users: User) @Delete fun deleteUsers(vararg uses:User) @Query("select * from user") fun queryEnabledUser() : LiveData<List<User>> }
  32. Databaseインスタンスの抽象クラスを作成 @Database(entities = [(User::class)], version = 1) abstract class AppDatabase

    : RoomDatabase() { abstract fun userDao(): UserDao }
  33. あとは使うだけ class MainActivity : AppCompatActivity() { lateinit var userList:LiveData<List<User>> override

    fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) userList = Room.databaseBuilder(applicationContext,AppDatabase::class.java,"sample.db") .build() .userDao() .queryEnabledUser() } } java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long period of time. at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:204) .allowMainThreadQueries()
  34. 別スレッド処理を簡単にしたい dependencies { def room_version = "1.1.0" // or, for

    latest rc, use "1.1.1-rc1" // optional - RxJava support for Room implementation "android.arch.persistence.room:rxjava2:$room_version" } @Dao interface UserDao { @Query("select * from user") fun queryEnabledUser() : Single<List<User>> } fun find(appDatabase: AppDatabase) { appDatabase.userDao().queryEnabledUser() .subscribeOn(Schedulers.computation()) .subscribe({ it -> userList.postValue(it) }) }
  35. トランザクションを扱いたい Single.fromCallable({ appDatabase.runInTransaction { appDatabase.userDao().updateUsers(User(10, "FirstCC", "Last" + counter, true))

    appDatabase.userDao().insertUsers(User(10, "First", "Last" + counter, true)) } } ).subscribeOn(Schedulers.computation()) .subscribe({ it -> counter++ find(appDatabase) }, { e -> Log.e("test", e.message) }) @Insert(onConflict = OnConflictStrategy.ABORT) fun insertUsers(vararg users: User) @Update fun updateUsers(vararg users: User)
  36. RoomのQuery例 @Dao interface UserDao { } @Query("select * from user")

    fun queryEnabledUserLive() : LiveData<List<User>> @Query("select * from user") fun queryEnabledUser() : List<User> @Query("SELECT * FROM user WHERE id = :id") fun queryGet(id:Long) : Maybe<User> @Query("SELECT MAX(id) FROM user ") fun queryMaxUserId() : Long?
  37. LiveData+ViewModel+Room ・Room:データベースへ簡単にアクセスしよう ・ViewModel:画面の回転時の処理を気にせず実装しよう ・LiveData:ライフサイクルを意識せずに UIに反映しよう

  38. まとめ ・Jetpack内のコンポーネントを使うことでより自分のやりたい作業に注力しよう ・Googleが提供するライブラリなので、最新バージョンへの追従も早い ・バックポートなどもきちんと提供されるはず 少しでも楽にAndroid開発をしよう!

  39. ご清聴ありがとうございました GDG神戸のDoorKeeperにもぜひご登録ください https://gdgkobe.doorkeeper.jp/