AndroidアプリのReduxスタイルの状態管理

この記事ではReduxスタイルの状態管理について説明していますが、正直なところ、次のアイデアは必ずしもReduxスタイルではなく、Reduxスタイルに触発されているため、Reduxに慣れていなくても問題ありません。また、このコンテキストでの「機能」は、ユーザーが操作する画面またはUIを表すために使用されます。


州および州の管理

状態管理は、さまざまなUIコントロールのすべての状態を一元化して、アプリケーション全体のデータフローを処理することにより、コンポーネント間の通信とデータ転送を容易にする方法です。これは、信頼できる唯一の情報源パラダイムの1つの考えられる兆候です。つまり、状態は単一のオブジェクトに格納され、その単一のオブジェクトがアプリケーションの唯一の真の状態ソースとして機能します。

簡単に言うと、UIの変更をトリガーするデータは何でもです。ソーシャルメディアアプリケーションの場合、状態の1つの例は投稿であり、UIを強制的に更新します。

その投稿内に、画像、キャプション、時間などのプロパティがある場合があります。画像とキャプションが不変であると仮定すると、「投稿レベル」では、一度設定すると実際には変更されないため、これら2つは状態とは見なされません。Time and Likesは変更可能で変更可能であり、UIを強制的に更新または更新するため、「投稿レベル」の状態になります。したがって、この検知された状態では、オブジェクトやソーシャルメディアの投稿のようなユーザー定義のデータ構造に限定されず、投稿のいいねの数(整数)などのプリミティブ型に拡張されます。

状態管理とアーキテクチャ

状態管理はアプリのアーキテクチャから独立していません。実際のところ、状態管理はアプリのアーキテクチャによるものです。SOLIDの原則によれば、例外的なアーキテクチャとは、モジュールが変更のために閉じられ、拡張のために開かれるアーキテクチャです。つまり、新しい機能をアプリに追加する場合、モジュールを変更して新しい機能に対応するのではなく、新しい機能を実装するモジュールにコードを追加し、追加したコードでそのモジュールの不変条件を保持する必要があります。変更のために閉じられ、拡張のために開かれます。

ユーザーが新機能を操作できる場合、それはステートフルです。一元化された状態管理があるということは、新しい機能の状態に対応するためにアプリの他の部分が変更されないことを意味しますが、その状態は、新しい機能がその状態にアクセスする中央状態の一部として追加されます。

上記のシナリオを視覚化するのは簡単ではありませんが、アプリケーションが複雑になるにつれて(新しい機能が追加されるにつれて)、状態管理の必要性が明らかになるというのがポイントです。これが、状態管理とそれをスケーラブルな方法で行う方法について考えることが必要な最初のステップである理由です。詳細に入る前に、まず状態を取り巻くプロパティと、Androidでそれらを抽象化する方法について考えてみましょう。

Androidアプリの各画面が機能を表す場合は、異なる機能間でのデータの共有や通信を回避することがアーキテクチャ上のベストプラクティスです。これは、機能を独立したモジュールに分離することでさらに強化できます。アプリ内のすべてのコンポーネントと通信する中央の状態リポジトリがあるWebアプリの状態管理とは異なり、ここではすべての機能に対して複数の状態リポジトリがあります。

たとえば、ビューモデルを取り上げます。通常、アプリのすべての機能を担当する中央のビューモデルではなく、アプリのすべての機能に個別のビューモデルを用意するのが理にかなっています。このように、状態が特定の機能のプロパティとして定義されている場合、各機能には独自の状態管理と状態管理があるため、2つの機能が相互に依存することはありません。

次の処理は、各機能が独立しており、独自の状態管理を持っているという上記のケースを想定しています。

Facebookのようなソーシャルメディアアプリを構築していると仮定します。私たちのタスクは、ユーザーがプロフィール写真、ユーザー名、略歴、友達、以前の投稿のリストなど、プロフィールの詳細を表示できるユーザープロフィール機能を構築することです。

この時点では、APIからのバックエンドと情報の取得には関心がありませんが、この特定のプロファイル機能の処理方法、レンダリング方法、およびユーザーアクションへの応答方法に関心があります。

主要コンポーネント

ここでも、他のすべての機能からの独立性を前提として、これを機能させるために必要な4つの主要コンポーネントを示します。

必要なもの:

  • メインプロファイルビュー—これは、すべてのプロファイル関連データをレンダリングするプロファイルのメイン画面または機能です。メインのフロントエンドコンポーネント。
  • プロファイルビューモデル—これは、リポジトリとデータソース、およびデータストア[共有設定]からすべてのデータを取得し、プロファイルビューで使用される状態オブジェクトを提供します。
  • プロファイルビューの状態—ビューモデルによって受信されたすべてのデータをマップし、このオブジェクトのプロパティがデータである単一の状態オブジェクトに変換する状態を表す不変のデータクラス。不変であるため、1つのデータが変更されると、既存の状態を変更するのではなく、関連する更新を含む新しい状態オブジェクトが作成されて返されます。これは、REDUXから借用した機能です。
  • プロファイルアクション—これには、プロファイルビューの範囲内にある間にユーザーが実行できるすべての可能なアクションの抽象的な署名が含まれます。これらのアクションには、プロフィール写真の編集、ユーザー名の更新、投稿の削除、友達の友達解除、設定への移動などがあります。ログイン画面の場合、一部のアクションはログインとパスワードの忘れです。

これらすべてのコンポーネントを実装する方法は次のとおりです。

行動

まず、プロファイル機能内から実行できるすべてのアクションを定義します。アクションは、開発している機能のタイプによって異なります。この例では、次のようなものがあります。

internal sealed class ProfileAction {
  
  // user refers to the user object containing profile information to be edited
  data class EditProfile(val user: User) : ProfileAction()
  
  // user id refers to the id of the user to unfollow
  data class Unfollow(val userId: Long) : ProfileAction()
  
  // refresh the current screen, requires no special data hence it is an object
  object Refresh : ProfileAction()
  
  // go to the previous screen
  object NavigateUp : ProfileAction()
  
  .
  .
  .
  // you could have more actions depending on your feature
  
}

プロファイルを編集するには、EditProfileアクションはユーザーが最初に編集する必要があり、同様にフォローを解除するには、アクションはフォローを解除するためにユーザーのIDを必要とします。

これと、オブジェクトを使用する特別なデータを必要としないアクションには、データクラスを使用します。内のすべてのアクションはProfileAction明示的に拡張する必要があります。その理由はすぐに明らかになります。

縦断ビューの状態

これは、ユーザープロファイル機能によって使用されるすべての状態を保持するオブジェクトです。不変であるため、保持しているデータを変更すると、変更されたデータを含むビューステートの新しいインスタンスが強制的に作成されます。これを実装する1つの方法があります。

@Immutable
internal data class ProfileViewState(
  val user: User = User.DEFAULT_USER,
  val friends: List<Friends> = emptyList(),
  val posts: List<Posts> = emptyList(),
  val refreshing: Boolean = false,
  .
  .
  .
  // you can have more data depending on your feature
) {
  companion object {
     val EMPTY = ProfileViewState()
  }
}

のすべてのプロパティを、ProfileViewState基本的に空の状態を表すいくつかのデフォルト値に初期化します。

プロファイルビューモデル

ViewModel、状態のみをビュー(プロファイル画面)に公開します。の実装ProfileViewModelはかなり複雑なので、実装か​​ら始めて簡単な説明を続けましょう。

Kotlinのコルーチンとフローに慣れていない場合は、混乱せずに詳細を説明することはできないため、好奇心旺盛な読者が、これから紹介する概念のいくつかについてさらに詳しく説明します。

あなたが本当に興味があるなら、あなたは間違いなくここで私のアプリ、GitHubをチェックするべきです。それをいじってからコードをチェックしてください、それは本当に役に立ちます。

@HiltViewModel
internal class ProfileViewModel @Inject constructor(
  private val updateUserInfo: UpdateUserInfo,
  observeUserInfo: ObserveUserInfo,
  private val updateFriendsList: UpdateFriendsList,
  observeFriendsList: ObserveFriendsList,
  private val updatePosts: UpdatePosts,
  observePosts: ObservePosts,
  .
  .
  .
  // you can always add more interactors and observers here
) : ViewModel() {
  
  private val loadingState = ObserveLoadingState() // returns a flow
  
  private val pendingActions = MutableSharedFlow<ShowDetailsAction>()
  
  // constructs the state and exposes it for the profile view to collect
  val state: StateFlow<ProfileViewState> = combine(
    observeUserInfo.flow,
    observeFriendsList.flow,
    observePosts.flow,
    loadingState.observable
  ) { user, friends, posts, refreshing ->
    ProfileViewState(
      user = user,
      friends = friends,
      posts = posts,
      refreshing = refreshing
    )
  }.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),
    initialValue = ProfileViewState.EMPTY
  )
  
  init {
    
    // listen for actions and execute them here
    viewModelScope.launch {
      pendingActions.collect {
        when (it) {
          Refresh -> refresh()
          is EditProfile -> editUserProfile(it)
          is Unfollow -> unfollow(it)
          else -> {} // do nothing
        }
      }
    }
  }
  
  // This is called by the profile view when a user performs some action that is 
  // defined in the ProfileAction class
  fun submitAction(action: ProfileAction) {
    viewModelScope.launch {
      pendingActions.emit(action)
    }
  }
  
  .
  .
  .
  // more functions down here
  
}

うわー!それは沢山。ここで理解しておくべき重要なことはProfileViewModel、状態を作成し、プロファイルビューからディスパッチされたアクションを実行する責任があるということです。

これは、アプリケーションのデータレイヤーにすでに実装されているインタラクターとオブザーバーを使用して行われます。これ以上進むと不便になります。

縦断ビュー

これは、Jetpackcomposeを使用して実装されたプロファイル機能のメインUIコンポーネントです。

@Composable
fun UserProfile(
    navigateUp: () -> Unit,
) {
    UserProfile(
        viewModel = hiltViewModel(),
        navigateUp = navigateUp,
    )
}

@Composable
internal fun UserProfile(
    viewModel: ProfileViewModel,
    navigateUp: () -> Unit,
) {
    val viewState by rememberFlowWithLifeCycle(flow = viewModel.state)
        .collectAsState(initial = ProfileViewState.EMPTY)
    UserProfile(
        state = viewState,
    ) {
        when (it) {
            is ProfileAction.NavigateUp -> navigateUp()
            else -> viewModel.submitAction(it)
        }
    }
}

@Composable
internal fun UserProfile(
  state: ProfileViewState,
  dispatcher: (ProfileAction) -> Unit
) {
  // define your layout here
  // if we want access to the user we can just call, state.user
  // we can do the same for posts and friends
}

プロファイルビューは、それを記述するために必要なすべてのデータを含む1つの状態オブジェクトを利用します。アクションは、ディスパッチャを使用して返送され、ProfileViewModelそこで処理されます。このように、プロファイルビューは、データのレイアウト、アクション、および状態がによってどのように処理されるかを記述することのみを担当しますViewModel。あなたが私に尋ねれば、それはかなりクールです。

状態管理は、アプリケーションの複雑さが増すにつれて便利になるため、プロジェクトの開始時に状態をどのように処理するかを考える必要があります。これはそれを行う1つの方法の例であり、ここですべてを理解する必要はありません。コアとなるアイデアだけで十分です。

これは本当に長い記事でした、そしてあなたがこれをはるかにうまくやったなら。ぶらぶらしてくれてありがとう。気を抜くな!

リンク:https ://betterprogramming.pub/redux-style-state-management-for-android-apps-62da15dc7578

#redux  #android 

What is GEEK

Buddha Community

AndroidアプリのReduxスタイルの状態管理

AndroidアプリのReduxスタイルの状態管理

この記事ではReduxスタイルの状態管理について説明していますが、正直なところ、次のアイデアは必ずしもReduxスタイルではなく、Reduxスタイルに触発されているため、Reduxに慣れていなくても問題ありません。また、このコンテキストでの「機能」は、ユーザーが操作する画面またはUIを表すために使用されます。


州および州の管理

状態管理は、さまざまなUIコントロールのすべての状態を一元化して、アプリケーション全体のデータフローを処理することにより、コンポーネント間の通信とデータ転送を容易にする方法です。これは、信頼できる唯一の情報源パラダイムの1つの考えられる兆候です。つまり、状態は単一のオブジェクトに格納され、その単一のオブジェクトがアプリケーションの唯一の真の状態ソースとして機能します。

簡単に言うと、UIの変更をトリガーするデータは何でもです。ソーシャルメディアアプリケーションの場合、状態の1つの例は投稿であり、UIを強制的に更新します。

その投稿内に、画像、キャプション、時間などのプロパティがある場合があります。画像とキャプションが不変であると仮定すると、「投稿レベル」では、一度設定すると実際には変更されないため、これら2つは状態とは見なされません。Time and Likesは変更可能で変更可能であり、UIを強制的に更新または更新するため、「投稿レベル」の状態になります。したがって、この検知された状態では、オブジェクトやソーシャルメディアの投稿のようなユーザー定義のデータ構造に限定されず、投稿のいいねの数(整数)などのプリミティブ型に拡張されます。

状態管理とアーキテクチャ

状態管理はアプリのアーキテクチャから独立していません。実際のところ、状態管理はアプリのアーキテクチャによるものです。SOLIDの原則によれば、例外的なアーキテクチャとは、モジュールが変更のために閉じられ、拡張のために開かれるアーキテクチャです。つまり、新しい機能をアプリに追加する場合、モジュールを変更して新しい機能に対応するのではなく、新しい機能を実装するモジュールにコードを追加し、追加したコードでそのモジュールの不変条件を保持する必要があります。変更のために閉じられ、拡張のために開かれます。

ユーザーが新機能を操作できる場合、それはステートフルです。一元化された状態管理があるということは、新しい機能の状態に対応するためにアプリの他の部分が変更されないことを意味しますが、その状態は、新しい機能がその状態にアクセスする中央状態の一部として追加されます。

上記のシナリオを視覚化するのは簡単ではありませんが、アプリケーションが複雑になるにつれて(新しい機能が追加されるにつれて)、状態管理の必要性が明らかになるというのがポイントです。これが、状態管理とそれをスケーラブルな方法で行う方法について考えることが必要な最初のステップである理由です。詳細に入る前に、まず状態を取り巻くプロパティと、Androidでそれらを抽象化する方法について考えてみましょう。

Androidアプリの各画面が機能を表す場合は、異なる機能間でのデータの共有や通信を回避することがアーキテクチャ上のベストプラクティスです。これは、機能を独立したモジュールに分離することでさらに強化できます。アプリ内のすべてのコンポーネントと通信する中央の状態リポジトリがあるWebアプリの状態管理とは異なり、ここではすべての機能に対して複数の状態リポジトリがあります。

たとえば、ビューモデルを取り上げます。通常、アプリのすべての機能を担当する中央のビューモデルではなく、アプリのすべての機能に個別のビューモデルを用意するのが理にかなっています。このように、状態が特定の機能のプロパティとして定義されている場合、各機能には独自の状態管理と状態管理があるため、2つの機能が相互に依存することはありません。

次の処理は、各機能が独立しており、独自の状態管理を持っているという上記のケースを想定しています。

Facebookのようなソーシャルメディアアプリを構築していると仮定します。私たちのタスクは、ユーザーがプロフィール写真、ユーザー名、略歴、友達、以前の投稿のリストなど、プロフィールの詳細を表示できるユーザープロフィール機能を構築することです。

この時点では、APIからのバックエンドと情報の取得には関心がありませんが、この特定のプロファイル機能の処理方法、レンダリング方法、およびユーザーアクションへの応答方法に関心があります。

主要コンポーネント

ここでも、他のすべての機能からの独立性を前提として、これを機能させるために必要な4つの主要コンポーネントを示します。

必要なもの:

  • メインプロファイルビュー—これは、すべてのプロファイル関連データをレンダリングするプロファイルのメイン画面または機能です。メインのフロントエンドコンポーネント。
  • プロファイルビューモデル—これは、リポジトリとデータソース、およびデータストア[共有設定]からすべてのデータを取得し、プロファイルビューで使用される状態オブジェクトを提供します。
  • プロファイルビューの状態—ビューモデルによって受信されたすべてのデータをマップし、このオブジェクトのプロパティがデータである単一の状態オブジェクトに変換する状態を表す不変のデータクラス。不変であるため、1つのデータが変更されると、既存の状態を変更するのではなく、関連する更新を含む新しい状態オブジェクトが作成されて返されます。これは、REDUXから借用した機能です。
  • プロファイルアクション—これには、プロファイルビューの範囲内にある間にユーザーが実行できるすべての可能なアクションの抽象的な署名が含まれます。これらのアクションには、プロフィール写真の編集、ユーザー名の更新、投稿の削除、友達の友達解除、設定への移動などがあります。ログイン画面の場合、一部のアクションはログインとパスワードの忘れです。

これらすべてのコンポーネントを実装する方法は次のとおりです。

行動

まず、プロファイル機能内から実行できるすべてのアクションを定義します。アクションは、開発している機能のタイプによって異なります。この例では、次のようなものがあります。

internal sealed class ProfileAction {
  
  // user refers to the user object containing profile information to be edited
  data class EditProfile(val user: User) : ProfileAction()
  
  // user id refers to the id of the user to unfollow
  data class Unfollow(val userId: Long) : ProfileAction()
  
  // refresh the current screen, requires no special data hence it is an object
  object Refresh : ProfileAction()
  
  // go to the previous screen
  object NavigateUp : ProfileAction()
  
  .
  .
  .
  // you could have more actions depending on your feature
  
}

プロファイルを編集するには、EditProfileアクションはユーザーが最初に編集する必要があり、同様にフォローを解除するには、アクションはフォローを解除するためにユーザーのIDを必要とします。

これと、オブジェクトを使用する特別なデータを必要としないアクションには、データクラスを使用します。内のすべてのアクションはProfileAction明示的に拡張する必要があります。その理由はすぐに明らかになります。

縦断ビューの状態

これは、ユーザープロファイル機能によって使用されるすべての状態を保持するオブジェクトです。不変であるため、保持しているデータを変更すると、変更されたデータを含むビューステートの新しいインスタンスが強制的に作成されます。これを実装する1つの方法があります。

@Immutable
internal data class ProfileViewState(
  val user: User = User.DEFAULT_USER,
  val friends: List<Friends> = emptyList(),
  val posts: List<Posts> = emptyList(),
  val refreshing: Boolean = false,
  .
  .
  .
  // you can have more data depending on your feature
) {
  companion object {
     val EMPTY = ProfileViewState()
  }
}

のすべてのプロパティを、ProfileViewState基本的に空の状態を表すいくつかのデフォルト値に初期化します。

プロファイルビューモデル

ViewModel、状態のみをビュー(プロファイル画面)に公開します。の実装ProfileViewModelはかなり複雑なので、実装か​​ら始めて簡単な説明を続けましょう。

Kotlinのコルーチンとフローに慣れていない場合は、混乱せずに詳細を説明することはできないため、好奇心旺盛な読者が、これから紹介する概念のいくつかについてさらに詳しく説明します。

あなたが本当に興味があるなら、あなたは間違いなくここで私のアプリ、GitHubをチェックするべきです。それをいじってからコードをチェックしてください、それは本当に役に立ちます。

@HiltViewModel
internal class ProfileViewModel @Inject constructor(
  private val updateUserInfo: UpdateUserInfo,
  observeUserInfo: ObserveUserInfo,
  private val updateFriendsList: UpdateFriendsList,
  observeFriendsList: ObserveFriendsList,
  private val updatePosts: UpdatePosts,
  observePosts: ObservePosts,
  .
  .
  .
  // you can always add more interactors and observers here
) : ViewModel() {
  
  private val loadingState = ObserveLoadingState() // returns a flow
  
  private val pendingActions = MutableSharedFlow<ShowDetailsAction>()
  
  // constructs the state and exposes it for the profile view to collect
  val state: StateFlow<ProfileViewState> = combine(
    observeUserInfo.flow,
    observeFriendsList.flow,
    observePosts.flow,
    loadingState.observable
  ) { user, friends, posts, refreshing ->
    ProfileViewState(
      user = user,
      friends = friends,
      posts = posts,
      refreshing = refreshing
    )
  }.stateIn(
    scope = viewModelScope,
    started = SharingStarted.WhileSubscribed(5000),
    initialValue = ProfileViewState.EMPTY
  )
  
  init {
    
    // listen for actions and execute them here
    viewModelScope.launch {
      pendingActions.collect {
        when (it) {
          Refresh -> refresh()
          is EditProfile -> editUserProfile(it)
          is Unfollow -> unfollow(it)
          else -> {} // do nothing
        }
      }
    }
  }
  
  // This is called by the profile view when a user performs some action that is 
  // defined in the ProfileAction class
  fun submitAction(action: ProfileAction) {
    viewModelScope.launch {
      pendingActions.emit(action)
    }
  }
  
  .
  .
  .
  // more functions down here
  
}

うわー!それは沢山。ここで理解しておくべき重要なことはProfileViewModel、状態を作成し、プロファイルビューからディスパッチされたアクションを実行する責任があるということです。

これは、アプリケーションのデータレイヤーにすでに実装されているインタラクターとオブザーバーを使用して行われます。これ以上進むと不便になります。

縦断ビュー

これは、Jetpackcomposeを使用して実装されたプロファイル機能のメインUIコンポーネントです。

@Composable
fun UserProfile(
    navigateUp: () -> Unit,
) {
    UserProfile(
        viewModel = hiltViewModel(),
        navigateUp = navigateUp,
    )
}

@Composable
internal fun UserProfile(
    viewModel: ProfileViewModel,
    navigateUp: () -> Unit,
) {
    val viewState by rememberFlowWithLifeCycle(flow = viewModel.state)
        .collectAsState(initial = ProfileViewState.EMPTY)
    UserProfile(
        state = viewState,
    ) {
        when (it) {
            is ProfileAction.NavigateUp -> navigateUp()
            else -> viewModel.submitAction(it)
        }
    }
}

@Composable
internal fun UserProfile(
  state: ProfileViewState,
  dispatcher: (ProfileAction) -> Unit
) {
  // define your layout here
  // if we want access to the user we can just call, state.user
  // we can do the same for posts and friends
}

プロファイルビューは、それを記述するために必要なすべてのデータを含む1つの状態オブジェクトを利用します。アクションは、ディスパッチャを使用して返送され、ProfileViewModelそこで処理されます。このように、プロファイルビューは、データのレイアウト、アクション、および状態がによってどのように処理されるかを記述することのみを担当しますViewModel。あなたが私に尋ねれば、それはかなりクールです。

状態管理は、アプリケーションの複雑さが増すにつれて便利になるため、プロジェクトの開始時に状態をどのように処理するかを考える必要があります。これはそれを行う1つの方法の例であり、ここですべてを理解する必要はありません。コアとなるアイデアだけで十分です。

これは本当に長い記事でした、そしてあなたがこれをはるかにうまくやったなら。ぶらぶらしてくれてありがとう。気を抜くな!

リンク:https ://betterprogramming.pub/redux-style-state-management-for-android-apps-62da15dc7578

#redux  #android