Kotlin-Rust Architecture #4

Open
opened 2022-10-14 17:48:26 +00:00 by Lonami · 4 comments

Most of the information here can be found on the App architecture guide.

We should probably aim for Unidirectional Data Flow:

.-- [__UI Layer__] <-.
|                    |
| Events        Data |
|                    |
'-> [_Data Layer_] --'

To achieve this, we'll need to figure out how to create some sort Observable which can be "resolved" from the Rust side, so that it integrates nicely with Compose and Kotlin.

The Observable acts sort-of like a Promise or Future which is eventually resolved, but it's important to use concepts compatible with Compose.

More precisely, this likely discards other ideas such as "async JNI calls", because they break the UDF model.

Most of the information here can be found on the [App architecture](https://developer.android.com/topic/architecture/intro) guide. We should probably aim for [Unidirectional Data Flow](https://youtu.be/AfCzIEwt_i4): ``` .-- [__UI Layer__] <-. | | | Events Data | | | '-> [_Data Layer_] --' ``` To achieve this, we'll need to figure out how to create some sort `Observable` which can be "resolved" from the Rust side, so that it integrates nicely with Compose and Kotlin. The `Observable` acts sort-of like a `Promise` or `Future` which is eventually resolved, but it's important to use concepts compatible with Compose. More precisely, this likely discards other ideas such as "async JNI calls", because they break the UDF model.
Poster
Owner

In the Get data from the internet course, it works through the following ViewModel:

sealed interface MarsUiState {
    data class Success(val photos: String) : MarsUiState
    object Error : MarsUiState
    object Loading : MarsUiState
}

class MarsViewModel : ViewModel() {
    var marsUiState: MarsUiState by mutableStateOf(MarsUiState.Loading)
        private set

    init {
        getMarsPhotos()
    }

    private fun getMarsPhotos() {
        viewModelScope.launch {
            marsUiState = try {
                val listResult = MarsApi.retrofitService.getPhotos()
                MarsUiState.Success("Success. ${listResult.size} Mars photos retrieved")
            } catch (e: IOException) {
                MarsUiState.Error
            } catch (e: HttpException) {
                MarsUiState.Error
            }
        }
    }
}

In essence, there's a mutableStateOf (the "observable") which is updated after a suspend fun (getPhotos) returns. We don't have this luxury with JNI.

But it may mean Kotlin should be the one responsible for "resolving" the observable.

In the [Get data from the internet](https://developer.android.com/codelabs/basic-android-kotlin-compose-getting-data-internet) course, it works through the following [`ViewModel`](https://github.com/google-developer-training/basic-android-kotlin-compose-training-mars-photos/blob/repo-starter/app/src/main/java/com/example/marsphotos/ui/screens/MarsViewModel.kt): ```kotlin sealed interface MarsUiState { data class Success(val photos: String) : MarsUiState object Error : MarsUiState object Loading : MarsUiState } class MarsViewModel : ViewModel() { var marsUiState: MarsUiState by mutableStateOf(MarsUiState.Loading) private set init { getMarsPhotos() } private fun getMarsPhotos() { viewModelScope.launch { marsUiState = try { val listResult = MarsApi.retrofitService.getPhotos() MarsUiState.Success("Success. ${listResult.size} Mars photos retrieved") } catch (e: IOException) { MarsUiState.Error } catch (e: HttpException) { MarsUiState.Error } } } } ``` In essence, there's a `mutableStateOf` (the "observable") which is updated after a `suspend fun` (`getPhotos`) returns. We don't have this luxury with JNI. But it may mean Kotlin should be the one responsible for "resolving" the observable.
Poster
Owner

produceState: convert non-Compose state into Compose state may be what we need to create these "observables".

[`produceState`: convert non-Compose state into Compose state](https://developer.android.com/jetpack/compose/side-effects#producestate) may be what we need to create these "observables".
Poster
Owner

Thinking in Compose is mental model, and warns about some dangerous side-effects [such as]:

  • Writing to a property of a shared object
  • Updating an observable in ViewModel
  • Updating shared preferences
It is important
  • Composable functions can execute in any order.
  • Composable functions can execute in parallel.
  • Recomposition skips as many composable functions and lambdas as possible.
  • Recomposition is optimistic and may be canceled.
  • A composable function might be run quite frequently, as often as every frame of an animation.
[Thinking in Compose](https://developer.android.com/jetpack/compose/mental-model) is mental model, and warns about some dangerous side-effects \[such as\]: * Writing to a property of a shared object * Updating an observable in `ViewModel` * Updating shared preferences \[It is important\] to be aware of when you program in Compose: * Composable functions can execute in any order. * Composable functions can execute in parallel. * Recomposition skips as many composable functions and lambdas as possible. * Recomposition is optimistic and may be canceled. * A composable function might be run quite frequently, as often as every frame of an animation.
Poster
Owner

The environment itself is also rather harsh. As described in Guide to app architecture:

...

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.

The environment itself is also rather harsh. As described in [Guide to app architecture](https://developer.android.com/topic/architecture): > \[...\] mobile devices are also resource-constrained, so at any time, the operating system might kill some app processes to make room for new ones. > > 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.
Sign in to join this conversation.
No Label
No Milestone
No project
No Assignees
1 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: Lonami/Talaria#4
There is no content yet.