10
4

More than 1 year has passed since last update.

Android DevelopersのGuide to app architectureの更新で気になったところを拾うメモ

Posted at

更新は色々あったようなんですが、基本的なところはそこまで更新されていなそうに見えました。個人的に面白かったところを太字にしておきます。本当にキャッチアップ用のメモです。

概要ページ

このSingle source of truthとUnidirectional Data Flowについてのまとめ的な部分が加わりました。この考え方自体を詳しく書いているところが今まではComposeのところしかなかったような気がするのでいいですね。

Single source of truth

When a new data type is defined in your app, you should assign a Single Source of Truth (SSOT) to it.

新しいデータ型を追加したらそれをSSOTに割り当てる必要がある。

The SSOT is the owner of that data, and only the SSOT can modify or mutate it.

SSOTはデータのオーナーでそこだけがデータを変更できる。

To achieve this, the SSOT exposes the data using an immutable type, and to modify the data, the SSOT exposes functions or receive events that other types can call.

これを達成するためにSSOTは変更不可能な型をexposeし、変更は関数やイベントを受け取ることによって行う。

このメリットは以下

It centralizes all the changes to a particular type of data in one place.
It protects the data so that other types cannot tamper with it.
It makes changes to the data more traceable. Thus, bugs are easier to spot.

データの変更を一つの場所にまとめる。
データを色んな所からいじれないようにする。
データを追いやすくするのでバグを見つけやすくする。

In an offline-first application, the source of truth for application data is typically a database. In some other cases, the source of truth can be a ViewModel or even the UI.

オフラインファーストなアプリならdatabaseがSSoTになるし、そうでないなら、ViewModelかUIがなりえる。

Unidirectional Data Flow

The single source of truth principle is often used in our guides with the Unidirectional Data Flow (UDF) pattern. In UDF, state flows in only one direction. The events that modify the data flow in the opposite direction.

state は一方向にだけ流れ、eventは反対方向にだけ流れる。

Android, state or data usually flow from the higher-scoped types of the hierarchy to the lower-scoped ones. Events are usually triggered from the lower-scoped types until they reach the SSOT for the corresponding data type. For example, application data usually flows from data sources to the UI. User events such as button presses flow from the UI to the SSOT where the application data is modified and exposed in an immutable type.

stateやデータは階層の高い部分から低い部分に通常流れる。
そしてEventは通常低いところから高いところに流れる。そして対応するデータ型のSSOTにたどり着くまで流れる。

This pattern better guarantees data consistency, is less prone to errors, is easier to debug and brings all the benefits of the SSOT pattern.

このパターンはより良いデータの整合性を保証し、エラーを少なくし、デバッグを容易にし、SSOTパターンの利益を得ることができる。

アーキテクチャの利益

Benefits of Architecture
Having a good Architecture implemented in your app brings a lot of benefits to the project and engineering teams:

  • It improves the maintainability, quality and robustness of the overall app.

アプリ全体のメンテナンス性、品質堅牢性

  • It allows the app to scale. More people and more teams can contribute to the same codebase with minimal code conflicts.

沢山の人が開発できるスケーラビリティ

  • It helps with onboarding. As Architecture brings consistency to your project, new members of the team can quickly get up to speed and be more efficient in less amount of time.

一貫性による効率的なオンボーディング

  • It is easier to test. A good Architecture encourages simpler types which are generally easier to test.

テスト簡単

  • Bugs can be investigated methodically with well defined processes.

明確なプロセスでバグを見つけることができる

Investing in Architecture also has a direct impact in your users. They benefit from a more stable application, and more features due to a more productive engineering team. However, Architecture also requires an up-front time investment. To help you justify this time to the rest of you company, take a look at these case studies where other companies share their success stories when having a good architecture in their app.

より安定したアプリになり、もっと機能を提供できるようになるので、ユーザーインパクトもある。
ただ、アーキテクチャは事前に投資が必要になる。
アーキテクチャを整えるために、他の会社の事例などを参考にすると良いかも。

UI-Layer

Business logicの定義の変化。

Business logic is what to do with state changes.

Business logic is the implementation of product requirements for app data.

UI-Layer events

Navigation events

Navigation eventsというのが追加になっている。
https://developer.android.com/topic/architecture/ui-layer/events#navigation-events

画面AからBに移動するときに、BからAにバックボタンででユーザーが戻ってきたときにもう一度Bに自動的に進まないコードが紹介されています。

個人的にこのコードには反対で、ViewModelでshouldMoveToNextPageみたいなStateを持って、移動する前にそれをfalseにしてから移動すればいいのではと思いました。(もしかするとなにかこれだとまずいライフサイクル関係でなにかあるのかも。)

UI-Layer State holders and UI State

コード例が追加されていたり、以下の警告が追加されていた。

ViewModelを別のComposable関数に渡さないというもの。

Warning: Don't pass ViewModel instances down to other composable functions. Doing so couples the composable function with the ViewModel type, making it less reusable and harder to test and preview. Also, there would be no clear single source of truth (SSOT) that manages the ViewModel instance. Passing the ViewModel down allows multiple composables to call ViewModel functions and modify its state, making bugs harder to debug. Instead, follow UDF best practices and pass down just the necessary state. Likewise, pass the propagating events up until they reach the ViewModel's composable SSOT. That is the SSOT which handles the event and calls the corresponding ViewModel methods.

またComposeでのstate holderの例が追加されたりしていた。

UI State production

そもそもこのドキュメントが新しい。

実際のアプリケーションだとサーバーからデータを貰いつつ、ユーザーからのイベント状態を変えるような、このようなコードはすごくあるんですが、何が正解なのかがよく分からなかった部分がカバーされるようになっていそうです。
ViewModelでのUiStateのまとめ方という感じ。以下のコードだけ見れば大体のイメージは掴めそう。

class TaskDetailViewModel @Inject constructor(
    private val tasksRepository: TasksRepository,
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val _isTaskDeleted = MutableStateFlow(false)
    private val _task = tasksRepository.getTaskStream(taskId)

    val uiState: StateFlow<TaskDetailUiState> = combine(
        _isTaskDeleted,
        _task
    ) { isTaskDeleted, task ->
        TaskDetailUiState(
            task = taskAsync.data,
            isTaskDeleted = isTaskDeleted
        )
    }
        // Convert the result to the appropriate observable API for the UI
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = TaskDetailUiState()
        )

    fun deleteTask() = viewModelScope.launch {
        tasksRepository.deleteTask(taskId)
        _isTaskDeleted.update { true }
    }
}

https://developer.android.com/topic/architecture/ui-layer/state-production#one-shot-and より

Domain-Layer

Data layer access restrictionというのが追加になった。

画像で見るとわかりやすい。
ちょっと面白いところとして、ここではトレードオフを話している。ここの面白い話として、どちらかというとUseCaseは必要なときだけあればいいんでは?という方向になっているところ。

image.png
https://developer.android.com/topic/architecture/domain-layer#data-access-restriction より

An advantage of making this restriction is that it stops your UI from bypassing domain layer logic, for example, if you are performing analytics logging on each access request to the data layer.

この制限を設定するメリットはドメインレイヤーを迂回させられなくできるということ。

However, the potentially significant disadvantage is that it forces you to add use cases even when they are just simple function calls to the data layer, which can add complexity for little benefit.

ただ、この重大になりえるデメリットは単純な呼び出しであってもUseCaseを追加しなくてはいけないこと。これは少ない利益に対して複雑さが増える。

A good approach is to add use cases only when required. If you find that your UI layer is accessing data through use cases almost exclusively, it may make sense to only access data this way.

一つの良いアプローチは必要な場合のみUseCaseを追加すること。 もしUseCaseからのみアクセスしてたらそこだけにするのも分かる。

Ultimately, the decision to restrict access to the data layer comes down to your individual codebase, and whether you prefer strict rules or a more flexible approach.

究極的にはあなた次第。

Build an offline-first app

こちらも割と新しいドキュメント。

どのようにオフラインで使えるアプリを作るかという話。
データを書き込むときにリモートに書きに行くのか、ローカルで書いてからコンフリクトを解決するのか、どのようなアプリでどの戦略でやるのがいいのかが書いてあります。
またデータの更新をアプリから取りに行くのか、サーバーから通知されて取りに行くのかなど、さまざまな戦略が見れます。

RemoteMediatorというPagingのクラスを使う実装がでてくるなどはここの特徴的な実装かもしれません。

10
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
4