Androidのシンプルアーキテクチャ

この記事では、Androidアプリのアーキテクチャについて、改めて纏めています。

### ポイント
Androidに関わらず、オブジェクト思考ではよく言われる、下記を守ることです。

- クラスをシンプルに
- 依存を少なく

### 技術要素
ここでは、下記の技術要素を使用しています。

- Androidの推奨アーキテクチャ  
https://developer.android.com/jetpack/guide?hl=ja#recommended-app-arch
- データバインディング  
https://developer.android.com/topic/libraries/data-binding?hl=ja
- AndroidでのDI  
https://developer.android.com/training/dependency-injection?hl=ja
- DataStore  
https://developer.android.com/topic/libraries/architecture/datastore?hl=ja

### 実装テーマ
- メモ画面。
- 保存先は、DataStore。

### クラス構成
構成は、下記を参考に、MemoFragment、MemoViewModel、MemoRepositoryです。
<img src="https://developer.android.com/topic/libraries/architecture/images/final-architecture.png?hl=ja" width=50%>

依存方向：MemoFragment　→　MemoViewModel　→　MemoRepository

### 実装内容
######　MemoFragment
ポイントはDataBinding。
DataBindingで、UI（xml）とソース（ここでは、ViewModel）を繋ぎます。

```kotlin:MemoFragment
class MemoFragment : Fragment() {
    private lateinit var binding: DataStoreFragmentBinding
    private val viewModel: MemoViewModel by viewModels()

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        binding = DataBindingUtil.inflate(inflater, R.layout.data_store_fragment, container, false)
        binding.lifecycleOwner = viewLifecycleOwner
        binding.viewModel = viewModel
        return binding.root
    }
    ...
```

DataBindingを使うために、app/build.gradleに下記を追加します。

```xml:app/build.gradle
    dataBinding {
        enabled = true
    }
```

layoutファイルは、layout階層を追加します。
dataには、MemoViewModelを追加します。
EditTextに`android:text="@={viewModel.text}"`を記載して、MemoViewModelのLiveDataと双方向にバインドさせます。（双方向のポイントは@と{の間の**=**）
Buttonには、`android:onClick="@{() -> viewModel.onClick()}"`を記載して、クリック時のメソッドをバインドします。

```xml:data_store_fragment.xml
<layout xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    <data>
        <variable
            name="viewModel"
            type="com.ykato.sample.kotlin.datastore.MemoViewModel" />
    </data>
        ...
        <EditText
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:gravity="top"
            android:text="@={viewModel.text}"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintBottom_toTopOf="@id/guideline" />
            ...
            <Button
                android:id="@+id/button"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="@string/save_button"
                android:onClick="@{() -> viewModel.onClick()}"/>
            ...
```

######　MemoViewModel
ViewModelはDI（Dependency Injection）を使用することで、依存度を下げます。
MemoRepositoryをメンバ変数として保持する場合、コンストラクタの引数としてInjectionすることで依存が下がります。（Mockもしやすく、テストもしやすくなります。）

```kotlin:MemoViewModel.kt
@HiltViewModel
class MemoViewModel @Inject constructor(
    private val store: MemoRepository
): ViewModel() {
...
```

DI(Hilt, Dagger)を行うために、依存関係をgradleファイルに追加します。

```xml:build.gradle
buildscript {
    ...
    dependencies {
        ...
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'
        ...
    }
}
```

```xml:app/build.gradle
...
apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'
...
android {
  ...
  compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_8
    targetCompatibility JavaVersion.VERSION_1_8
  }
  ...
}
...
dependencies {
    implementation "com.google.dagger:hilt-android:2.35"
    kapt "com.google.dagger:hilt-android-compiler:2.35"
}
...
```

また、DIを行う為には、Applicationクラスに`@HiltAndroidApp`を追加する必要があります。

```kotlin:KotlinSampleApplication.kt
@HiltAndroidApp
class KotlinSampleApplication: Application()
```

MemoFragmentとMainActivityには`@AndroidEntryPoint`を追加する必要があります。
（MemoFragmentをMainActivity上に追加しているため、MainActivityにも追加する必要がある。）

```kotlin:MainActivity.kt
@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
...
```

```kotlin:MemoFragment.kt
@AndroidEntryPoint
class MemoFragment : Fragment() {
...
```

######　MemoRepository
MemoRepositoryはDataStoreを使用して、メモの内容を保存します。
また、メモの内容をキャッシュする（DataStoreの読み込み・保存は非同期処理なので、キャッシュして同期メソッドを実装する）ため、Singletonにしています。

```kotlin:MemoRepository.kt
val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "memo")
val MEMO_STRING = stringPreferencesKey("memo_string")

@Singleton
class MemoRepository @Inject constructor(
    @ApplicationContext private val context: Context,
) {
    private val cache by lazy { AtomicReference<String?>(null) }
...
```
DataStoreを使用するために、依存に下記を追加

```xml:app/build.gradle
...
dependencies {
    implementation("androidx.datastore:datastore-preferences:1.0.0-rc01")
...
```

### 参考
今回のコードは下記にあります。
https://github.com/yoshihiro-kato/android-samples/tree/05a032ade61babbc8cf133418fc367a5a091a33f/KotlinSampleApplication/app/src/main/java/com/ykato/sample/kotlin/datastore