20220109のAndroidに関する記事は3件です。

Guide to app architectureからプラクティスを拾うメモ UIレイヤー編

上記から拾ってメモしておきます ちょっと分かりにくい部分として、最初に書いておきたいのが、UIレイヤーとUIは別で、UIレイヤーにはState holder(ViewModelなど)も含み、UIやUI elementはActivityなどを指すようです。 https://developer.android.com/jetpack/guide/ui-layer?hl=en より (あとでまとめ記事的なのを作るかも?) Activityなどのアプリのコンポーネントにアプリのデータや状態を持ってはいけない Given the conditions of this environment, it's possible for your app components to be launched individually and out-of-order, and the operating system or user can destroy them at any time. Because these events aren't under your control, you shouldn't store or keep in memory any application data or state in your app components, and your app components shouldn't depend on each other. なぜ? メモリ内のデータはアプリの管理下になく、いつでも消され得るため。 activities, fragments, services, content providersなどのコンポーネント同士が依存しあってはならない Given the conditions of this environment, it's possible for your app components to be launched individually and out-of-order, and the operating system or user can destroy them at any time. Because these events aren't under your control, you shouldn't store or keep in memory any application data or state in your app components, and your app components shouldn't depend on each other. なぜ? 個別にコンポーネントは起動され、いつでもシステムによって消され得るため。 すべてをActivityやFragmentに書いてはならない The most important principle to follow is separation of concerns. It's a common mistake to write all your code in an Activity or a Fragment. These UI-based classes should only contain logic that handles UI and operating system interactions. By keeping these classes as lean as possible, you can avoid many problems related to the component lifecycle, and improve the testability of these classes. なぜ? separation of concerns、関心事の分離のため。 ActivityやFragmentはUIやOSとの連携のみを行い、できるだけ小さくする The most important principle to follow is separation of concerns. It's a common mistake to write all your code in an Activity or a Fragment. These UI-based classes should only contain logic that handles UI and operating system interactions. By keeping these classes as lean as possible, you can avoid many problems related to the component lifecycle, and improve the testability of these classes. なぜ? コンポーネントのライフサイクルとの問題を避け、クラスのテスト可能性を向上させる。 UIは完全に持続する(persistent)データモデルによって動かされるべき Another important principle is that you should drive your UI from data models, preferably persistent models. Data models represent the data of an app. They're independent from the UI elements and other components in your app. This means that they are not tied to the UI and app component lifecycle, but will still be destroyed when the OS decides to remove the app's process from memory. 持続する(persistent)というのはUIエレメントや他のアプリのコンポーネントから独立しており、アプリのライフサイクルに縛られていないということ。ただし、OSが削除を決めた場合はメモリから削除される。 なぜ? Persistent models are ideal for the following reasons: - Your users don't lose data if the Android OS destroys your app to free up resources. - Your app continues to work in cases when a network connection is flaky or not available. If you base your app architecture on data model classes, you make your app more testable and robust. OSがアプリのリソースを解放したときにデータを失わなくて済むため。 ネットワーク接続が不安定だったり、使えない場合にアプリを動作し続けさせるため。 またデータモデルクラスによるアーキテクチャベースになっているとアプリはテスト可能で堅牢になる。 アプリでは最低でも2レイヤーが必要になる Considering the common architectural principles mentioned in the previous section, each application should have at least two layers: The UI layer that displays application data on the screen. The data layer that contains the business logic of your app and exposes application data. You can add an additional layer called the domain layer to simplify and reuse the interactions between the UI and data layers. https://developer.android.com/jetpack/guide?hl=en#recommended-app-arch より なぜ? 上記の原則に則るため。 UIは完全に持続する(persistent)データモデルによって動かされるべき Activityなどのアプリのコンポーネントにアプリのデータや状態を持ってはいけない UIはアプリケーションデータを画面に表示し、データが変わったときやユーザーインタラクションでその変更を反映するアップデートする The role of the UI layer (or presentation layer) is to display the application data on the screen. Whenever the data changes, either due to user interaction (such as pressing a button) or external input (such as a network response), the UI should update to reflect the changes. The UI layer is made up of two things: * UI elements that render the data on the screen. You build these elements using Views or Jetpack Compose functions. * State holders (such as ViewModel classes) that hold data, expose it to the UI, and handle logic. ここから以下も読む。 The role of the UI is to display the application data on the screen and also to serve as the primary point of user interaction. Whenever the data changes, either due to user interaction (like pressing a button) or external input (like a network response), the UI should update to reflect those changes. Effectively, the UI is a visual representation of the application state as retrieved from the data layer. The term UI refers to UI elements such as activities and fragments that display the data, independent of what APIs they use to do this (Views or Jetpack Compose). Because the role of the data layer is to hold, manage, and provide access to the app data, the UI layer must perform the following steps: Consume app data and transform it into data the UI can easily render. Consume UI-renderable data and transform it into UI elements for presentation to the user. Consume user input events from those assembled UI elements and reflect their effects in the UI data as needed. Repeat steps 1 through 3 for as long as necessary. UI layerは以下のステップを行わなければならない。 UIがかんたんに表示できるようにデータを変換する 表示できるデータを使って、UIエレメントとしてユーザーに表示する 作ったUIからUserの入力を受け取って、UIのdataに必要に応じて適応する 上記のステップを必要な限り繰り返す なぜ? シンプルにアプリケーションのデータを表示するのがUIの役割と定義しているため。 UI Stateを定義する the information required to fully render the UI can be encapsulated in a NewsUiState data class defined as follows: UIで情報を表示するための情報をdata classのNewsUiStateで囲う data class NewsUiState( val isSignedIn: Boolean = false, val isPremium: Boolean = false, val newsItems: List<NewsItemUiState> = listOf(), val userMessages: List<Message> = listOf() ) data class NewsItemUiState( val title: String, val body: String, val bookmarked: Boolean = false, ... ) なぜ? 後述のUDFを実現するために必要になるため。 UI StateクラスはImmutableにし、UIでUI Stateを変更してはならない DON'T bookmarkButton.setOnClickListener { // データレイヤーで保持しているデータと競合する newsUiState.bookmarked = true } The UI state definition in the example above is immutable. The key benefit of this is that immutable objects provide guarantees regarding the state of the application at an instant in time. This frees up the UI to focus on a single role: to read the state and update its UI elements accordingly. As a result, you should never modify the UI state in the UI directly unless the UI itself is the sole source of its data. Violating this principle results in multiple sources of truth for the same piece of information, leading to data inconsistencies and subtle bugs. 上記のUI Stateクラスは変更できず、immutableになっている。このメリットはある瞬間のアプリケーションの状態を保証してくれること。これによってUIは状態を読み取り、それに応じてUI要素を更新するという単一の役割に集中できる。 なぜ? この原則に違反すると同じ情報を持つ情報源が複数になり、データの不整合や微妙なバグを引き起こす。原則を守ることでSingle source of truthにすることができる。 Unidirectional Data Flow(UDF)によるデータの管理をする https://developer.android.com/jetpack/guide/ui-layer?hl=en より。ブックマークボタンを押したときの流れ。 These interactions may benefit from a mediator to process them, defining the logic to be applied to each event and performing the requisite transformations to the backing data sources in order to create UI state. These interactions and their logic may be housed in the UI itself, but this can quickly get unwieldy as the UI starts to become more than its name suggests: it becomes data owner, producer, transformer, and more. Furthermore, this can affect testability because the resulting code is a tightly coupled amalgam with no discernable boundaries. Ultimately, the UI stands to benefit from reduced burden. Unless the UI state is very simple, the UI's sole responsibility should be to consume and display UI state. データの所有者やアプリケーションのデータを変換するものなどの名前以上の役割を持ってしまい、それがテストを難しくする。 The pattern where the state flows down and the events flow up is called a unidirectional data flow (UDF). stateの流れを下に流し、イベントを上に上げるのがUDF。 なぜUDFにするか? Why use UDF? UDF models the cycle of state production as shown in Figure 4. It also separates the place where state changes originate, the place where they are transformed, and the place where they are finally consumed. This separation lets the UI do exactly what its name implies: display information by observing state changes, and relay user intent by passing those changes on to the ViewModel. In other words, UDF allows for the following: Data consistency. There is a single source of truth for the UI. Testability. The source of state is isolated and therefore testable independent of the UI. Maintainability. Mutation of state follows a well-defined pattern where mutations are a result of both user events and the sources of data they pull from. データの一貫性。Single source of truthにできる。 テスト。stateが分離されているので、UIの独立性が高まる メンテナビリティ。状態の変化が、明確に定義されたパターンに従っている。 UIの唯一の責務はUI Stateを消費して表示することであるべき These interactions may benefit from a mediator to process them, defining the logic to be applied to each event and performing the requisite transformations to the backing data sources in order to create UI state. These interactions and their logic may be housed in the UI itself, but this can quickly get unwieldy as the UI starts to become more than its name suggests: it becomes data owner, producer, transformer, and more. Furthermore, this can affect testability because the resulting code is a tightly coupled amalgam with no discernable boundaries. Ultimately, the UI stands to benefit from reduced burden. Unless the UI state is very simple, the UI's sole responsibility should be to consume and display UI state. なぜ? コードの境界のはっきりしない密結合の集合体となって、テスト容易性に影響を与える可能性があるため。 ViewModelで観測可能なLiveDataやStateFlowなどのデータホルダーでデータを保持する After you define your UI state and determine how you will manage the production of that state, the next step is to present the produced state to the UI. Because you're using UDF to manage the production of state, you can consider the produced state to be a stream—in other words, multiple versions of the state will be produced over time. As a result, you should expose the UI state in an observable data holder like LiveData or StateFlow. The reason for this is so that the UI can react to any changes made in the state without having to manually pull data directly from the ViewModel. These types also have the benefit of always having the latest version of the UI state cached, which is useful for quick state restoration after configuration changes. LiveDataやStateFlow、またはComposeのStateを使う。 なぜ? UIがデータを手動で引っ張ってくる必要なしにデータを反映でき、最新のデータを保持するという性質もあり、これらはStateの復元に役立つ。 UiStateのクラスでデータをラップする In cases where the data exposed to the UI is relatively simple, it's often worth wrapping the data in a UI state type because it conveys the relationship between the emission of the state holder and its associated screen or UI element. Furthermore, as the UI element grows more complex, it’s always easier to add to the definition of the UI state to accommodate the extra information needed to render the UI element. なぜ? 画面やUI要素とそのデータの関連が分かりやすくなるため。またUI要素に必要なデータが増えたときに拡張しやすくなる。 UiStateが複数ある場合は相互に関連するUiStateそれぞれをうまく扱う必要がある。 DON'T class NewsViewModel { val bookmarkCount: StateFlow<Int> val articles: StateFlow<List<Article>> // 片方だけ更新されるとバグる fun bookmark() { bookmarkCount.value = bookmarkCount.value++ } } A UI state object should handle states that are related to each other. This leads to fewer inconsistencies and it makes the code easier to understand. If you expose the list of news items and the number of bookmarks in two different streams, you might end up in a situation where one was updated and the other was not. When you use a single stream, both elements are kept up to date. なぜ? 例えばブックマークの数とニュースのリストが有ったときに片方だけ更新すると不整合が起こるため。 UiStateを関連する状態を処理できるようにするために単一のクラスにまとめる Furthermore, some business logic may require a combination of sources. For example, you might need to show a bookmark button only if the user is signed in and that user is a subscriber to a premium news service. You could define a UI state class as follows: data class NewsUiState( val isSignedIn: Boolean = false, val isPremium: Boolean = false, val newsItems: List<NewsItemUiState> = listOf() ) val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium In this declaration, the visibility of the bookmark button is a derived property of two other properties. As business logic gets more complex, having a singular UiState class where all properties are immediately available becomes increasingly important. なぜ? 例えば、ログインしていて、プレミアムの場合だけボタンを出したいみたいな場合に、クラスがまとまっていないとクラスをまたいで利用できないため。 StateFlowやLiveDataなどのUiStateを適切に分割する UI states: single stream or multiple streams? The key guiding principle for choosing between exposing UI state in a single stream or in multiple streams is the previous bullet point: the relationship between the items emitted. The biggest advantage to a single-stream exposure is convenience and data consistency: consumers of state always have the latest information available at any instant in time. However, there are instances where separate streams of state from the ViewModel might be appropriate: Unrelated data types: Some states that are needed to render the UI might be completely independent from each other. In cases like these, the costs of bundling these disparate states together might outweigh the benefits, especially if one of these states is updated more frequently than the other. UiState diffing: The more fields there are in a UiState object, the more likely it is that the stream will emit as a result of one of its fields being updated. Because views don't have a diffing mechanism to understand whether consecutive emissions are different or the same, every emission causes an update to the view. This means that mitigation using the Flow APIs or methods like distinctUntilChanged() on the LiveData might be necessary. StateFlowなどを一つにする利点はデータの一貫性。最新の状態を入手できること。ただ、以下の場合で分けるほうが適切な場合がある。 無関係なデータ 完全に独立していて、他の状態よりも頻繁に更新される場合に、コストが1つにするメリットを上回る場合がある。 変更を見る必要がある 一つのUiStateにたくさんのデータがある場合は、データが流れやすくなり、すべてのviewが更新されるので、UI側でdistinctUntilChanged()のような軽減が必要になる。 ViewModelから呼び出される処理はメインスレッドから呼び出しても安全であるべき Any work performed in a ViewModel should be main-safe—safe to call from the main thread. This is because the data and domain layers are responsible for moving work to a different thread. なぜ? ドメインレイヤーやデータレイヤーに別スレッドで動かす責務があるため。 Pagingライブラリを使う場合はUiStateに入れずフィールドを分ける The Paging library is consumed in the UI with a type called PagingData. Because PagingData represents and contains items that can change over time—in other words, it is not an immutable type—it should not be represented in an immutable UI state. Instead, you should expose it from the ViewModel independently in its own stream. See the Android Paging codelab for a specific example of this. なぜ? PagingData自体が時間とともに変わるので、不変データの中に入れることができないため。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

EditTextのカーソルの行の位置を取得するには

EditTextのカーソル位置を取得するのは、調べたりするとすぐ出てきたりしますが、カーソルが何行目なのかを取得する方法が、無かったので忘れないようにする為、書いておく事にします。 EditTextのカーソルの行の位置を取得する方法 val layout: Layout = binding.editText.layout val offset: Int = binding.editText.selectionEnd // カーソルの位置 val line = layout.getLineForOffset(offset) //引数に指定したoffsetの行番号(カーソルが何行目か) val primaryHorizontal = layout.getPrimaryHorizontal(offset) // 引数に指定したoffsetの水平位置が返ってきます(カーソルが左端からの水平位置) EditTextのLayoutを使う事で、取得することができました。 カーソルを上に移動させる方法 binding.editText.setSelection(layout.getOffsetForHorizontal(line -1, primaryHorizontal)) 実際に使用するときは一番上の行なのかを判定したり、工夫が必要になると思います。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

flutter_native_splash使ってみた

要約 Splashとは? flutter_native_splashを使ってみる Android実装時の注意点 OS 12の注意点 Flutter2.5以上ではAndroidManifestのmeta dateを消す 感想 参考記事 Splashとは? アプリを立ち上げるときに一瞬表示される画面のことです。 flutter_native_splashを使ってみる pubspec.yamlにライブラリとflutter_native_splashの設定を追加する pubspec.yaml dependencies: flutter_native_splash: ^1.3.3 flutter_native_splash: color: "#FFFFFF" image: images/hogehoge.png android: true ios: true // ローカルに保存されている画像を呼び出せるよにする assets: - images/ Android実装時の注意点 OS 12の注意点 AndroidOS12ではデフォルトでSplashが実装されてます。 デフォルトのSplashを削除することはできないため、 Android実装時の注意点 Flutter2.5からはio.flutter.embedding.android.SplashScreenDrawableは不要なため削除すると以下のLogは表示されなくなります。 A splash screen was provided to Flutter, but this is deprecated. See flutter.dev/go/android-splash-migration for migration steps. AndroidManifest.xml <meta-data android:name="io.flutter.embedding.android.SplashScreenDrawable" android:resource="@drawable/launch_background" /> 感想 とても簡単に画面を作らずにSplashを表示できる。 しかし、SplashにAccessTokenの処理を書きたい、Splashの表示時間を長くしたいなど、場合によってはこのライブラリを使わない可能性もある。 参考記事
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む