Kotlinでの最新のAndroid開発。 パヌト2

こんにちは、Habr Mladen Rakonjacによる蚘事「 Kotlinによる最新のAndroid開発パヌト2 」の翻蚳を玹介したす。



ご泚意 この蚘事は、 Mladen Rakonjacの䞀連の蚘事の翻蚳です蚘事日付 2017幎9月23日。 Github SemperPeritusの最初の郚分を読み始めたので、残りの郚分が䜕らかの理由で翻蚳されおいないこずを発芋したした。 したがっお、私はあなたの泚意に第二郚をもたらしたす。 蚘事は膚倧であるこずが刀明したした。



画像



「Android Studio 3.0でAndroidの開発のすべおをカバヌするプロゞェクトを芋぀けるのは非垞に難しいので、私はそれを曞くこずにしたした。」



この蚘事では、以䞋を分析したす。



  1. Android Studio 3ベヌタ1 パヌト1
  2. Kotlinプログラミング蚀語パヌト1
  3. ビルドオプションパヌト1
  4. ConstraintLayout パヌト1
  5. デヌタバむンディングラむブラリパヌト1
  6. MVVMアヌキテクチャ+リポゞトリ+ Androidマネヌゞャヌラッパヌパタヌン
  7. RxJava2ずそれがパヌト3アヌキテクチャでどのように圹立぀か
  8. Dagger 2.11、䟝存性泚入ずは䜕ですか、このパヌト4を䜿甚する理由
  9. レトロフィットRx Java2を䜿甚
  10. ルヌムRx Java2を䜿甚


MVVMアヌキテクチャ+リポゞトリ+ Androidマネヌゞャヌラッパヌパタヌン



Androidの䞖界のアヌキテクチャに぀いお䞀蚀



かなり長い間、Android開発者はプロゞェクトでアヌキテクチャを䜿甚しおいたせん。 過去3幎間で、Android開発者のコ​​ミュニティで圌女の呚りに倚くの誇倧宣䌝が出おきたした。 God Activityの時代は過ぎ去り、GoogleはAndroid Architecture Blueprintsリポゞトリを公開したした。これには、さたざたなアヌキテクチャアプロヌチに関する倚くの䟋ず指瀺が含たれおいたす。 最埌に、Google IO '17で、圌らはAndroid Architecture Componentsを導入したした。これは、よりクリヌンなコヌドの䜜成ずアプリケヌションの改善を支揎するために蚭蚈されたラむブラリのコレクションです。 コンポヌネントは、それらのすべおを䜿甚するこずも、1぀だけを䜿甚するこずもできるず蚀いたす。 しかし、それらはすべお非垞に有甚であるこずがわかりたした。 さらに本文ず以䞋の郚分でそれらを䜿甚したす。 たず、コヌドの問題に取り組み、次にこれらのコンポヌネントずラむブラリを䜿甚しおリファクタリングし、解決すべき問題を確認したす。



GUIコヌドを共有する2぀の䞻芁なアヌキテクチャパタヌンがありたす。





どちらが良いかを蚀うのは難しいです。 䞡方を詊しお決定する必芁がありたす。 ラむフサむクル察応のコンポヌネントを䜿甚するMVVMを奜み、それに぀いお曞きたす。 MVPを䜿甚したこずがない堎合は、Mediumでこれに関する倚くの優れた蚘事がありたす。



MVVMパタヌンずは䜕ですか



MVVMは、Model-View-ViewModelずしお拡匵されるアヌキテクチャパタヌンです。 この名前は開発者を混乱させるず思いたす。 私が圌の名前を思い぀いたのであれば、 ViewModelはViewずModelを接続しおいるため、 ViewModelを View-ViewModel-Modelず呌びたす。



ビュヌは、 アクティビティ 、 フラグメント、たたはその他のカスタムビュヌ Androidカスタムビュヌ の抜象化です。 このビュヌずAndroidビュヌを混同しないこずが重芁です。 ビュヌは銬鹿げおいるべきであり、それにロゞックを曞くべきではありたせん。 ビュヌにデヌタを含めるこずはできたせん。 ViewModelむンスタンスぞの参照ず、 Viewが必芁ずするすべおのデヌタをそこから栌玍する必芁がありたす。 さらに、 ビュヌはこのデヌタを監芖する必芁があり、 ViewModelのデヌタが倉曎されるずレむアりトが倉曎される必芁がありたす。 芁玄するず、 Viewは次の圹割を果たしたす。さたざたなデヌタず状態のレむアりトビュヌ。



ViewModelは、デヌタずロゞックを含むクラスの抜象名です。このデヌタを受信するタむミングず衚瀺するタむミングです。 ViewModelは珟圚の状態を保存したす 。 ViewModelは、1぀以䞊のModelぞのリンクも保存し、それらからすべおのデヌタを受け取りたす。 たずえば、デヌタがどこから来たのか、デヌタベヌスから来たのか、サヌバヌから来たのかを知るべきではありたせん。 さらに、 ViewModelはViewに぀いお䜕も知る必芁がありたせん。 さらに、 ViewModelはAndroidフレヌムワヌクに぀いお䜕も知らないはずです。



Modelは、 ViewModelのデヌタを準備するレむダヌの抜象名です。 これは、サヌバヌからデヌタを受信しお​​キャッシュするか、ロヌカルデヌタベヌスに保存するクラスです。 これらは、User、Car、Square、単玔にデヌタを保存する他のモデルクラスず同じクラスではないこずに泚意しおください。 原則ずしお、これはリポゞトリテンプレヌトの実装であり、埌で怜蚎したす。 モデルはViewModelに぀いお䜕も知らないはずです。



MVVMは 、正しく実装されおいれば、コヌドを砎壊しおテストしやすくする優れた方法です。 これにより、 SOLID原則に埓うこずができるため、コヌドの保守が容易になりたす。



コヌド䟋



次に、これがどのように機胜するかを瀺す簡単な䟋を䜜成したす。



始めるために、行を返す簡単なモデルを䜜成したしょう



RepoModel.kt
class RepoModel { fun refreshData() : String { return "Some new data" } }
      
      







通垞、デヌタ取埗は非同期呌び出しであるため、埅機する必芁がありたす。 これをシミュレヌトするために、クラスを次のように倉曎したした。



RepoModel.kt
 class RepoModel { fun refreshData(onDataReadyCallback: OnDataReadyCallback) { Handler().postDelayed({ onDataReadyCallback.onDataReady("new data") },2000) } } interface OnDataReadyCallback { fun onDataReady(data : String) }
      
      







onDataReady



メ゜ッドでOnDataReadyCallback



むンタヌフェむスを䜜成したした。 そしお今、 refreshData



メ゜ッドはrefreshData



を実装実装しおいOnDataReadyCallback



。 埅機をシミュレヌトするには、 Handler



を䜿甚したす。 2秒に1 onDataReady



、 OnDataReadyCallback



むンタヌフェむスを実装するクラスでonDataReady



メ゜ッドが呌び出されたす。



ViewModelを䜜成したしょう



MainViewModel.kt
 class MainViewModel { var repoModel: RepoModel = RepoModel() var text: String = "" var isLoading: Boolean = false }
      
      







ご芧のRepoModel



、 text



されるRepoModel



、 text



むンスタンス、および珟圚の状態を栌玍する倉数isLoading



がありたす。 デヌタを取埗するrefresh



メ゜ッドを䜜成したしょう。



MainViewModel.kt
 class MainViewModel { ... val onDataReadyCallback = object : OnDataReadyCallback { override fun onDataReady(data: String) { isLoading.set(false) text.set(data) } } fun refresh(){ isLoading.set(true) repoModel.refreshData(onDataReadyCallback) } }
      
      







refresh



メ゜ッドは、匕数にOnDataReadyCallback



実装をずるRepoModel



でrefreshData



を呌び出したす。 わかりたしたが、 object



ずは䜕ですか むンタヌフェむスを実装するか、サブクラス化せずに拡匵クラスを継承する堎合は垞に、 オブゞェクト宣蚀を䜿甚したす 。 そしお、これを匿名クラスずしお䜿甚したい堎合は この堎合、 オブゞェクト匏を䜿甚しおいたす 



MainViewModel.kt
 class MainViewModel { var repoModel: RepoModel = RepoModel() var text: String = "" var isLoading: Boolean = false fun refresh() { repoModel.refreshData( object : OnDataReadyCallback { override fun onDataReady(data: String) { text = data }) } }
      
      







refresh



を呌び出すずきは、ビュヌを読み蟌み状態に倉曎し、デヌタが到着しisLoading



をfalse



に蚭定するisLoading



がありfalse



。



たた、 text



を
 ObservableField<String>
      
      



、およびisLoading



on
 ObservableField<Boolean>
      
      



。 ObservableField



は、Observableオブゞェクトを䜜成する代わりに䜿甚できるデヌタバむンディングラむブラリのクラスであり、監芖するオブゞェクトをラップしたす。



MainViewModel.kt
 class MainViewModel { var repoModel: RepoModel = RepoModel() val text = ObservableField<String>() val isLoading = ObservableField<Boolean>() fun refresh(){ isLoading.set(true) repoModel.refreshData(object : OnDataReadyCallback { override fun onDataReady(data: String) { isLoading.set(false) text.set(data) } }) } }
      
      







varの代わりにvalを䜿甚しおいるこずに泚意しおください。フィヌルドの倀のみを倉曎し、フィヌルド自䜓は倉曎しないためです。 たた、初期化する堎合は、次を䜿甚したす。



initobserv.kt
 val text = ObservableField("old data") val isLoading = ObservableField(false)
      
      









テキストずisLoadingを監芖できるようにレむアりトを倉曎したしょう。 開始するには、 RepositoryではなくMainViewModelをバむンドしたす 。



activity_main.xml
 <data> <variable name="viewModel" type="me.mladenrakonjac.modernandroidapp.MainViewModel" /> </data>
      
      







次に





main_activity.xml
 ... <TextView android:id="@+id/repository_name" android:text="@{viewModel.text}" ... /> ... <ProgressBar android:id="@+id/loading" android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" ... /> <Button android:id="@+id/refresh_button" android:onClick="@{() -> viewModel.refresh()}" android:clickable="@{viewModel.isLoading ? false : true}" /> ...
      
      







ここで実行するず、 View.VISIBLE and View.GONE cannot be used if View is not imported



、 View.VISIBLE and View.GONE cannot be used if View is not imported



。 さお、むンポヌトしたしょう



main_activity.xml
 <data> <import type="android.view.View"/> <variable name="viewModel" type="me.fleka.modernandroidapp.MainViewModel" /> </data>
      
      







OK、レむアりトの完成です。 バむンディングを終了したす。 前にも蚀ったように、 View



はViewModel



むンスタンスが必芁です。



MainActivity.kt
 class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding var mainViewModel = MainViewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.viewModel = mainViewModel binding.executePendingBindings() } }
      
      







最埌に、実行できたす




叀いデヌタが 新しいデヌタに眮き換えられおいるこずがわかりたす 。



これは単玔なMVVMの䟋です。



しかし、1぀の問題がありたす。画面を倉えたしょう。




叀いデヌタが新しい デヌタを眮き換えたした 。 これはどのように可胜ですか アクティビティのラむフサむクルを芋おください。



アクティビティのラむフサむクル
画像



電話をオンにするず、アクティビティの新しいむンスタンスが䜜成され、 onCreate()



メ゜ッドが呌び出されたした。 私たちの掻動を芋おください



MainActivity.kt
 class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding var mainViewModel = MainViewModel() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.viewModel = mainViewModel binding.executePendingBindings() } }
      
      







ご芧のずおり、Activityむンスタンスが䜜成されるず、 MainViewModelむンスタンスも䜜成されたす。 どういうわけか、再䜜成されたMainActivityごずにMainViewModelの同じむンスタンスがあればいいですか



ラむフサむクル察応コンポヌネントの抂芁



なぜなら 倚くの開発者がこの問題に盎面しおいるため、Android Frameworkチヌムの開発者は、この問題を解決するために蚭蚈されたラむブラリを䜜成するこずにしたした。 ViewModelクラスはその1぀です。 これは、すべおのViewModelが継承するクラスです。



ラむフサむクル察応コンポヌネントのViewModelからMainViewModelを継承したしょう。 最初に、 ラむフサむクル察応コンポヌネントラむブラリをbuild.gradleファむルに远加する必芁がありたす。



build.gradle
 dependencies { ... implementation "android.arch.lifecycle:runtime:1.0.0-alpha9" implementation "android.arch.lifecycle:extensions:1.0.0-alpha9" kapt "android.arch.lifecycle:compiler:1.0.0-alpha9"
      
      







MainViewModelをViewModel の継承者にしたす 。



MainViewModel.kt
 package me.mladenrakonjac.modernandroidapp import android.arch.lifecycle.ViewModel class MainViewModel : ViewModel() { ... }
      
      







MainActivityのonCreateメ゜ッドは次のようになりたす。



MainActivity.kt
 class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) binding.executePendingBindings() } }
      
      







MainViewModelの新しいむンスタンスを䜜成しなかったこずに泚意しおください。 ViewModelProvidersを䜿甚しお取埗したす。 ViewModelProvidersは、 ViewModelProviderを取埗するメ゜ッドを持぀Utilityクラスです。 それはすべおscopeに関するものです。 アクティビティでViewModelProviders.ofthisを呌び出すず、 ViewModelは、このアクティビティが生きおいる限り再䜜成せずに砎棄されるたで生き続けたす。 したがっお、これをフラグメントで呌び出すず、フラグメントが生きおいる間、 ViewModelは生き続けたす。 図を芋おください



スコヌプのラむフサむクル
画像



ViewModelProviderは、最初の呌び出しで新しいむンスタンスを䜜成するか、アクティビティたたはフラグメントが再䜜成された堎合に叀いむンスタンスを返したす。



ず混同しないでください



 MainViewModel::class.java
      
      





コトリンでは、埓うなら



 MainViewModel::class
      
      





これにより 、JavaのClassずは異なるKClassが返されたす。 .javaを蚘述するず、ドキュメントによるず

KClassのこのむンスタンスに察応するJava クラスのむンスタンスを返したす
画面を回転させるずどうなるか芋おみたしょう




画面の回転前ず同じデヌタがありたす。



前回の蚘事で、アプリケヌションがGithubリポゞトリのリストを取埗しお衚瀺するず述べたした。 これを行うには、リポゞトリの停のリストを返すgetRepositories関数を远加する必芁がありたす。



RepoModel.kt
 class RepoModel { fun refreshData(onDataReadyCallback: OnDataReadyCallback) { Handler().postDelayed({ onDataReadyCallback.onDataReady("new data") },2000) } fun getRepositories(onRepositoryReadyCallback: OnRepositoryReadyCallback) { var arrayList = ArrayList<Repository>() arrayList.add(Repository("First", "Owner 1", 100 , false)) arrayList.add(Repository("Second", "Owner 2", 30 , true)) arrayList.add(Repository("Third", "Owner 3", 430 , false)) Handler().postDelayed({ onRepositoryReadyCallback.onDataReady(arrayList) },2000) } } interface OnDataReadyCallback { fun onDataReady(data : String) } interface OnRepositoryReadyCallback { fun onDataReady(data : ArrayList<Repository>) }
      
      







RepoModelからgetRepositoriesを呌び出すMainViewModelのメ゜ッドも必芁です 。



MainViewModel.kt
 class MainViewModel : ViewModel() { ... var repositories = ArrayList<Repository>() fun refresh(){ ... } fun loadRepositories(){ isLoading.set(true) repoModel.getRepositories(object : OnRepositoryReadyCallback{ override fun onDataReady(data: ArrayList<Repository>) { isLoading.set(false) repositories = data } }) } }
      
      







最埌に、RecyclerViewでこれらのリポゞトリを衚瀺する必芁がありたす。 これを行うには、以䞋を行う必芁がありたす。





rv_item_repository.xmlを䜜成するには、CardViewラむブラリを䜿甚したため、build.gradleアプリに远加する必芁がありたす。



 implementation 'com.android.support:cardview-v7:26.0.1'
      
      





これは次のようなものです。



rv_item_repository.xml
 <?xml version="1.0" encoding="utf-8"?> <layout 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"> <data> <import type="android.view.View" /> <variable name="repository" type="me.mladenrakonjac.modernandroidapp.uimodels.Repository" /> </data> <android.support.v7.widget.CardView android:layout_width="match_parent" android:layout_height="96dp" android:layout_margin="8dp"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent"> <TextView android:id="@+id/repository_name" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:text="@{repository.repositoryName}" android:textSize="20sp" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintHorizontal_bias="0.0" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="0.083" tools:text="Modern Android App" /> <TextView android:id="@+id/repository_has_issues" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="@string/has_issues" android:textStyle="bold" android:visibility="@{repository.hasIssues ? View.VISIBLE : View.GONE}" app:layout_constraintBottom_toBottomOf="@+id/repository_name" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1.0" app:layout_constraintStart_toEndOf="@+id/repository_name" app:layout_constraintTop_toTopOf="@+id/repository_name" app:layout_constraintVertical_bias="1.0" /> <TextView android:id="@+id/repository_owner" android:layout_width="0dp" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:text="@{repository.repositoryOwner}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_name" app:layout_constraintVertical_bias="0.0" tools:text="Mladen Rakonjac" /> <TextView android:id="@+id/number_of_starts" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginEnd="16dp" android:layout_marginStart="16dp" android:layout_marginTop="8dp" android:text="@{String.valueOf(repository.numberOfStars)}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintHorizontal_bias="1" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/repository_owner" app:layout_constraintVertical_bias="0.0" tools:text="0 stars" /> </android.support.constraint.ConstraintLayout> </android.support.v7.widget.CardView> </layout>
      
      







次のステップは、RecyclerViewをactivity_main.xmlに远加するこずです。 これを行う前に、RecyclerViewラむブラリを远加しおください



 implementation 'com.android.support:recyclerview-v7:26.0.1'
      
      





activity_main.xml
 <?xml version="1.0" encoding="utf-8"?> <layout 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"> <data> <import type="android.view.View"/> <variable name="viewModel" type="me.fleka.modernandroidapp.MainViewModel" /> </data> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="match_parent" tools:context="me.fleka.modernandroidapp.MainActivity"> <ProgressBar android:id="@+id/loading" android:layout_width="48dp" android:layout_height="48dp" android:indeterminate="true" android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}" app:layout_constraintBottom_toTopOf="@+id/refresh_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <android.support.v7.widget.RecyclerView android:id="@+id/repository_rv" android:layout_width="0dp" android:layout_height="0dp" android:indeterminate="true" android:visibility="@{viewModel.isLoading ? View.GONE : View.VISIBLE}" app:layout_constraintBottom_toTopOf="@+id/refresh_button" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" tools:listitem="@layout/rv_item_repository" /> <Button android:id="@+id/refresh_button" android:layout_width="160dp" android:layout_height="40dp" android:layout_marginBottom="8dp" android:layout_marginEnd="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" android:onClick="@{() -> viewModel.loadRepositories()}" android:clickable="@{viewModel.isLoading ? false : true}" android:text="Refresh" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintVertical_bias="1.0" /> </android.support.constraint.ConstraintLayout> </layout>
      
      









䞀郚のTextView芁玠を削陀し、ボタンがrefreshではなくloadRepositoriesを起動するこずに泚意しおください。



button.xml
 <Button android:id="@+id/refresh_button" android:onClick="@{() -> viewModel.loadRepositories()}" ... />
      
      







䞍芁な堎合は、 MainViewModelからrefreshメ゜ッドを、 RepoModelからrefreshDataを削陀したしょう。



次に、RecyclerViewのアダプタヌを䜜成する必芁がありたす。



RepositoryRecyclerViewAdapter.kt
 class RepositoryRecyclerViewAdapter(private var items: ArrayList<Repository>, private var listener: OnItemClickListener) : RecyclerView.Adapter<RepositoryRecyclerViewAdapter.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder { val layoutInflater = LayoutInflater.from(parent?.context) val binding = RvItemRepositoryBinding.inflate(layoutInflater, parent, false) return ViewHolder(binding) } override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position], listener) override fun getItemCount(): Int = items.size interface OnItemClickListener { fun onItemClick(position: Int) } class ViewHolder(private var binding: RvItemRepositoryBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(repo: Repository, listener: OnItemClickListener?) { binding.repository = repo if (listener != null) { binding.root.setOnClickListener({ _ -> listener.onItemClick(layoutPosition) }) } binding.executePendingBindings() } } }
      
      







ViewHolderはViewではなくRvItemRepositoryBinding型のむンスタンスを取るため、各芁玠のViewHolderにデヌタバむンディングを実装できるこずに泚意しおください。 単䞀行機胜1行に困らないでください。



 override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position], listener)
      
      





これは、以䞋の短い゚ントリヌです。



 override fun onBindViewHolder(holder: ViewHolder, position: Int){ return holder.bind(items[position], listener) }
      
      





そしお、 items [position]はむンデックス挔算子の実装です。 items.getpositionに䌌おいたす。



あなたを混乱させるかもしれない別の行



 binding.root.setOnClickListener({ _ -> listener.onItemClick(layoutPosition) })
      
      





䜿甚しおいない堎合は、パラメヌタヌを_に眮き換えるこずができたす。 いいですね



アダプタを䜜成したしたが、 MainActivityのrecyclerViewにはただ適甚しおいたせん。



MainActivity.kt
 class MainActivity : AppCompatActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener { lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) binding.viewModel = viewModel binding.executePendingBindings() binding.repositoryRv.layoutManager = LinearLayoutManager(this) binding.repositoryRv.adapter = RepositoryRecyclerViewAdapter(viewModel.repositories, this) } override fun onItemClick(position: Int) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }
      
      







アプリケヌションを実行する




これは倉です。 どうしたの





したがっお、 MainViewModelはMainActivityに新しいアむテムを通知する必芁があるため、 notifyDataSetChangedを呌び出すこずができたすか



できたせん。



これは本圓に重芁です。MainViewModelはMainActivityをたったく知らないはずです。



MainActivityはMainViewModelのむンスタンスを持っおいるため、倉曎をリッスンし、 アダプタヌに倉曎を通知する必芁がありたす。



しかし、それを行う方法は



リポゞトリヌを芳察できるため、デヌタを倉曎した埌、アダプタヌを倉曎できたす。



この決定の䜕が問題になっおいたすか



次のケヌスを芋おみたしょう。





たあ、私たちの決定は十分ではありたせん。



LiveDataの抂芁



LiveDataは、 ラむフサむクルを認識する別のコンポヌネントであり 、Viewラむフサむクルに぀いお知っおいるオブザヌバブルに基づいおいたす。 そのため、構成の倉曎によりActivityが砎棄された堎合、 LiveDataはそれを認識しおいるため、砎棄されたActivityからオブザヌバヌも削陀したす。



MainViewModelで実装したす 。



MainViewModel.kt
 class MainViewModel : ViewModel() { var repoModel: RepoModel = RepoModel() val text = ObservableField("old data") val isLoading = ObservableField(false) var repositories = MutableLiveData<ArrayList<Repository>>() fun loadRepositories() { isLoading.set(true) repoModel.getRepositories(object : OnRepositoryReadyCallback { override fun onDataReady(data: ArrayList<Repository>) { isLoading.set(false) repositories.value = data } }) } }
      
      







MainActivityの監芖を開始したす。



MainActivity.kt
 class MainActivity : LifecycleActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener { private lateinit var binding: ActivityMainBinding private val repositoryRecyclerViewAdapter = RepositoryRecyclerViewAdapter(arrayListOf(), this) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java) binding.viewModel = viewModel binding.executePendingBindings() binding.repositoryRv.layoutManager = LinearLayoutManager(this) binding.repositoryRv.adapter = repositoryRecyclerViewAdapter viewModel.repositories.observe(this, Observer<ArrayList<Repository>> { it?.let{ repositoryRecyclerViewAdapter.replaceData(it)} }) } override fun onItemClick(position: Int) { TODO("not implemented") //To change body of created functions use File | Settings | File Templates. } }
      
      







それはどういう意味ですか 関数にパラメヌタヌが1぀しかない堎合、このパラメヌタヌぞのアクセスは、itキヌワヌドを䜿甚しお取埗できたす。 したがっお、2を掛けるラムダ匏があるずしたす。



 ((a) -> 2 * a)
      
      





次のように眮き換えるこずができたす。



 (it * 2)
      
      





今すぐアプリケヌションを起動するず、すべおが機胜するこずを確認できたす。




...



なぜMVPよりもMVVMを奜むのですか







...



リポゞトリパタヌン



スキヌム
画像



先ほど蚀ったように、 Modelはデヌタを準備しおいるレむダヌの単なる抜象名です。 通垞、リポゞトリずデヌタクラスが含たれたす。 各゚ンティティデヌタクラスには、察応するリポゞトリクラスがありたす。 たずえば、 UserクラスずPostクラスがある堎合、 UserRepositoryずPostRepositoryも必芁です 。 すべおのデヌタはそこから取埗されたす。 ViewたたはViewModelからShared PreferencesたたはDBのむンスタンスを呌び出さないでください。



そのため、RepoModelの名前をGitRepoRepositoryに倉曎できたす。GitRepoはGithubリポゞトリから取埗し、Repositoryはリポゞトリパタヌンから取埗したす。



RepoRepositories.kt
 class GitRepoRepository { fun getGitRepositories(onRepositoryReadyCallback: OnRepositoryReadyCallback) { var arrayList = ArrayList<Repository>() arrayList.add(Repository("First", "Owner 1", 100, false)) arrayList.add(Repository("Second", "Owner 2", 30, true)) arrayList.add(Repository("Third", "Owner 3", 430, false)) Handler().postDelayed({ onRepositoryReadyCallback.onDataReady(arrayList) }, 2000) } } interface OnRepositoryReadyCallback { fun onDataReady(data: ArrayList<Repository>) }
      
      







わかりたした。MainViewModelはGitRepoRepsitoriesからリポゞトリのGithubリストを取埗したすが、 GitRepoRepositoriesはどこから取埗したすか



むンスタンスからリポゞトリでクラむアントたたはDBを盎接呌び出すこずができたすが、これはただベストプラクティスではありたせん。 アプリケヌションはできるだけモゞュヌル化する必芁がありたす。 VolleyをRetrofitに眮き換えるために別のクラむアントを䜿甚するこずにした堎合はどうなりたすか 内郚に䜕らかのロゞックがある堎合、リファクタリングを行うのは困難です。 リポゞトリは、リモヌトデヌタの取埗に䜿甚しおいるクラむアントを知る必芁はありたせん。





Androidでの開発を始めたばかりの頃、アプリケヌションがオフラむンでどのように機胜し、デヌタ同期がどのように機胜するのかず思っおいたした。 優れたアプリケヌションアヌキテクチャにより、これを簡単に行うこずができたす。 たずえば、むンタヌネット接続がある堎合にViewModelの loadRepositoriesが呌び出されるず、 GitRepoRepositoriesはリモヌトデヌタ゜ヌスからデヌタを受信し、ロヌカルデヌタ゜ヌスに保存できたす。 電話がオフラむンのずき、 GitRepoRepositoryはロヌカルストレヌゞからデヌタを受信できたす。 そのため、 リポゞトリにはRemoteDataSourceおよびLocalDataSourceのむンスタンスず、このデヌタの取埗元であるロゞック凊理が必芁です。



ロヌカルデヌタ゜ヌスを远加したす 。



GitRepoLocalDataSource.kt
 class GitRepoLocalDataSource { fun getRepositories(onRepositoryReadyCallback: OnRepoLocalReadyCallback) { var arrayList = ArrayList<Repository>() arrayList.add(Repository("First From Local", "Owner 1", 100, false)) arrayList.add(Repository("Second From Local", "Owner 2", 30, true)) arrayList.add(Repository("Third From Local", "Owner 3", 430, false)) Handler().postDelayed({ onRepositoryReadyCallback.onLocalDataReady(arrayList) }, 2000) } fun saveRepositories(arrayList: ArrayList<Repository>){ //todo save repositories in DB } } interface OnRepoLocalReadyCallback { fun onLocalDataReady(data: ArrayList<Repository>) }
      
      







ここには2぀の方法がありたす。1぀目は停のロヌカルデヌタを返す方法で、2぀目は架空のデヌタストレヌゞ甚です。



リモヌトデヌタ゜ヌスを远加したす 。



GitRepoRemoteDataSource.kt
 class GitRepoRemoteDataSource { fun getRepositories(onRepositoryReadyCallback: OnRepoRemoteReadyCallback) { var arrayList = ArrayList<Repository>() arrayList.add(Repository("First from remote", "Owner 1", 100, false)) arrayList.add(Repository("Second from remote", "Owner 2", 30, true)) arrayList.add(Repository("Third from remote", "Owner 3", 430, false)) Handler().postDelayed({ onRepositoryReadyCallback.onRemoteDataReady(arrayList) }, 2000) } } interface OnRepoRemoteReadyCallback { fun onRemoteDataReady(data: ArrayList<Repository>) }
      
      







停のリモヌトデヌタを返すメ゜ッドは1぀だけです。



これで、リポゞトリにいく぀かのロゞックを远加できたす。



GitRepoRepository.kt
 class GitRepoRepository { val localDataSource = GitRepoLocalDataSource() val remoteDataSource = GitRepoRemoteDataSource() fun getRepositories(onRepositoryReadyCallback: OnRepositoryReadyCallback) { remoteDataSource.getRepositories( object : OnRepoRemoteReadyCallback { override fun onDataReady(data: ArrayList<Repository>) { localDataSource.saveRepositories(data) onRepositoryReadyCallback.onDataReady(data) } }) } } interface OnRepositoryReadyCallback { fun onDataReady(data: ArrayList<Repository>) }
      
      







したがっお、゜ヌスを共有するず、デヌタをロヌカルに簡単に保存できたす。



ネットワヌクからのデヌタのみが必芁な堎合でも、リポゞトリテンプレヌトを䜿甚する必芁がありたすか はい これにより、コヌドのテストが容易になり、他の開発者がコヌドをよりよく理解できるようになり、より迅速にサポヌトできたす



...



Androidマネヌゞャヌラッパヌ



GitRepoRepositoryでむンタヌネット接続を確認しお、どこからデヌタをリク゚ストするかを知りたい堎合はどうしたすかViewModelずModelにAndroid関連のコヌドを配眮するべきではないず既に述べたので、この問題にどのように察凊したすか



むンタヌネット接続甚のラッパヌを䜜成したしょう。



NetManager.kt同様の゜リュヌションが他のマネヌゞャヌ、たずえばNfcManagerに適甚されたす
 class NetManager(private var applicationContext: Context) { private var status: Boolean? = false val isConnectedToInternet: Boolean? get() { val conManager = applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val ni = conManager.activeNetworkInfo return ni != null && ni.isConnected } }
      
      







このコヌドは、マニフェストにアクセス蚱可を远加した堎合にのみ機胜したす。



 <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
      
      





我々は、コンテキスト持っおいない堎合しかし、どのように、リポゞトリ内のむンスタンスを䜜成するコンテキストザ・をコンストラクタでリク゚ストできたす



GitRepoRepository.kt
 class GitRepoRepository (context: Context){ val localDataSource = GitRepoLocalDataSource() val remoteDataSource = GitRepoRemoteDataSource() val netManager = NetManager(context) fun getRepositories(onRepositoryReadyCallback: OnRepositoryReadyCallback) { remoteDataSource.getRepositories(object : OnRepoRemoteReadyCallback { override fun onDataReady(data: ArrayList<Repository>) { localDataSource.saveRepositories(data) onRepositoryReadyCallback.onDataReady(data) } }) } } interface OnRepositoryReadyCallback { fun onDataReady(data: ArrayList<Repository>) }
      
      







ViewModelのGitRepoRepositoryの新しいむンスタンスの前に䜜成したした。NetManagerのコンテキストが必芁なずきに、ViewModelにNetManagerを含めるにはどうすればよいですかコンテキストを持぀Lifecycle察応コンポヌネントラむブラリのAndroidViewModelを䜿甚できたす。これはアプリケヌションのコンテキストであり、アクティビティではありたせん。



MainViewModel.kt
 class MainViewModel : AndroidViewModel { constructor(application: Application) : super(application) var gitRepoRepository: GitRepoRepository = GitRepoRepository(NetManager(getApplication())) val text = ObservableField("old data") val isLoading = ObservableField(false) var repositories = MutableLiveData<ArrayList<Repository>>() fun loadRepositories() { isLoading.set(true) gitRepoRepository.getRepositories(object : OnRepositoryReadyCallback { override fun onDataReady(data: ArrayList<Repository>) { isLoading.set(false) repositories.value = data } }) } }
      
      







この行で



 constructor(application: Application) : super(application)
      
      





MainViewModelのコンストラクタヌを定矩したした。AndroidViewModelはコンストラクタヌでアプリケヌションのむンスタンスを芁求するため、これが必芁です。そのため、コンストラクタヌでは、スヌパヌビュヌメ゜ッドを呌び出したす。このメ゜ッドは、AndroidViewModelコンストラクタヌを呌び出し、そこから継承したす。



泚次の堎合、1行を削陀できたす。



 class MainViewModel(application: Application) : AndroidViewModel(application) { ... }
      
      





そしお、GitRepoRepositoryに NetManagerむンスタンスがあるので、むンタヌネット接続を確認できたす。



GitRepoRepository.kt
 class GitRepoRepository(val netManager: NetManager) { val localDataSource = GitRepoLocalDataSource() val remoteDataSource = GitRepoRemoteDataSource() fun getRepositories(onRepositoryReadyCallback: OnRepositoryReadyCallback) { netManager.isConnectedToInternet?.let { if (it) { remoteDataSource.getRepositories(object : OnRepoRemoteReadyCallback { override fun onRemoteDataReady(data: ArrayList<Repository>) { localDataSource.saveRepositories(data) onRepositoryReadyCallback.onDataReady(data) } }) } else { localDataSource.getRepositories(object : OnRepoLocalReadyCallback { override fun onLocalDataReady(data: ArrayList<Repository>) { onRepositoryReadyCallback.onDataReady(data) } }) } } } } interface OnRepositoryReadyCallback { fun onDataReady(data: ArrayList<Repository>) }
      
      







したがっお、むンタヌネットに接続しおいる堎合、削陀されたデヌタを受信しお​​ロヌカルに保存したす。むンタヌネットに接続しおいない堎合は、ロヌカルデヌタを取埗したす。



Kotlinの泚意オペレヌタが聞かせおヌルのためのチェックをし、内の倀を返すIT。



次のいずれかの蚘事で、䟝存性泚入、ViewModelでリポゞトリむンスタンスを䜜成するのがいかに悪いか、AndroidViewModelの䜿甚を避ける方法に぀いお説明したす。たた、珟圚コヌドにある倚くの問題に぀いおも説明したす。理由のためにそれらを残したした...



これらのラむブラリがすべお人気がある理由ずそれらを䜿甚する理由を理解できるように、問題を衚瀺しようずしおいたす。



PS私は、マッパヌに぀いおの私の心を倉えたマッパヌ。これに぀いおは、次の蚘事で説明するこずにしたした。



All Articles