d

    Daniele B

    2 years ago
    I set up my code like this, with a viewModel which is platform-independent:
    class MainActivity : AppCompatActivity() {
    
        val viewModel = MyViewModel()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyTextComponent(viewModel.state)
            }
        }
    }
    
    @Composable
    @ExperimentalCoroutinesApi
    fun MyTextComponent(stateFlow: StateFlow<DataModel>) {
        val state by stateFlow.collectAsState()
        Text(text = state.mytext)
    }
    But on configuration changes (e.g. device rotation), the Activity gets destroyed, and so the viewModel. Is it better to tie the viewModel to the App class, or which other strategy do you suggest?
    Halil Ozercan

    Halil Ozercan

    2 years ago
    by definition, ViewModel should survive activity orientation changes. However, to achieve that you should use proper
    ViewModelProvider
    to instantiate your ViewModel. Easiest way to do that would be depending on KTX and using
    viewModels
    delegate. You can look at the example in here https://developer.android.com/kotlin/ktx#fragment
    d

    Daniele B

    2 years ago
    Well, I am actually using my own ViewModel, not the Android viewModel
    I am aiming to make the app purely multi-platform
    do you suggest to wrap my viewModel into Android’s ViewModel?
    Halil Ozercan

    Halil Ozercan

    2 years ago
    sorry for jumping the gun then 🙂 Yes, extending on Android's ViewModel API would be better.
    d

    Daniele B

    2 years ago
    I was expecting that on Compose, we just need to work with data, and not with Android’s ViewModel
    Halil Ozercan

    Halil Ozercan

    2 years ago
    Android's ViewModel is definitely not a requirement. You can create your own state objects and use
    remember
    and
    rememberSavedInstanceState
    to initialize them inside composable components.
    d

    Daniele B

    2 years ago
    Oh I see, I was not aware of those those 2 functions. I must research. Do you have any tutorial/article I can read about them?
    d

    Daniele B

    2 years ago
    class JetnewsApplication : Application() {
    
        lateinit var container: AppContainer
    
        override fun onCreate() {
            super.onCreate()
            container = AppContainerImpl(this)
        }
    }
    
    
    class MainActivity : AppCompatActivity() {
    
        val navigationViewModel by viewModels<NavigationViewModel>()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            val appContainer = (application as JetnewsApplication).container
            setContent {
                JetnewsApp(appContainer, navigationViewModel)
            }
        }
    }
    I just realized in Google’s JetNews application, they use this approach to retain objects
    I have done this way, it seems to work well:
    class TheApp : Application() {
    
        lateinit var viewModel: ViewModel
    
        override fun onCreate() {
            super.onCreate()
            viewModel = ViewModel()
        }
    }
    
    class MainActivity : AppCompatActivity() {
    
        lateinit var viewModel : ViewModel
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            viewModel = (application as TheApp).viewModel
            setContent {
                CovidAppTheme {
                    MainLayout(viewModel)
                }
            }
        }
    }
    i

    Ian Lake

    2 years ago
    JetNews uses Android ViewModels (that's the
    by viewModels()
    call). Creating a Singleton is a terrible alternative since that means it never gets cleared out, unlike Android ViewModels (which do get cleared when the activity is finished)
    remember
    uses a ViewModel under the hood as that is the only way to properly save data across configuration changes
    d

    Daniele B

    2 years ago
    Hi @Ian Lake thanks! I had a look at
    remember
    , but I don’t understand how to apply in the case of
    StateFlow
    . This is my code:
    class MainActivity : AppCompatActivity() {
    
        val viewModel = MyViewModel()
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContent {
                MyTextComponent(viewModel.state)
            }
        }
    }
    
    @Composable
    @ExperimentalCoroutinesApi
    fun MyTextComponent(stateFlow: StateFlow<DataModel>) {
        val state by stateFlow.collectAsState()
        Text(text = state.mytext)
    }
    i

    Ian Lake

    2 years ago
    Yeah, really looks like you should be using Android ViewModels
    d

    Daniele B

    2 years ago
    So, you said
    remember
    is using the Android ViewModel under the hood, right?
    so, I guess it’s just the matter to apply remember to this code
    but I don’t understand how
    In case of StateFlow, I was expecting it made sense to tie it to the Application
    my ViewModel passed via StateFlow is an object linked to a data repository, which manages the whole caching mechanism for the webservices, so I would want it to persist for the lifetime of the app
    so, it’s not just the matter of the data that is viewed on the UI of the app, but also the business logic cached data that is not shown in the UI
    i

    Ian Lake

    2 years ago
    MVVM is usually built in top of a repository layer, yes.
    d

    Daniele B

    2 years ago
    @Ian Lake sorry, to ask again, but I still haven’t understood when using StateFlow to pass the data (which is not an Android ViewModel), where I should define the object and how I can make it persist. So, you said
    remember
    is the way, but as far as I understand “remember” should be applied inside components, while my StateFlow object is defined inside the Activity.
    i

    Ian Lake

    2 years ago
    You should use an Android ViewModel if you're building objects outside of a
    @Composable
    that needs to be retained across config changes
    d

    Daniele B

    2 years ago
    OK, I see, so I guess I need to wrap the StateFlow inside the Android ViewModel
    The Android ViewModel examples use LiveData, but StateFlow is actually a replacement of that. So I guess I can use StateFlow inside the Android ViewModel instead of LiveData.
    i

    Ian Lake

    2 years ago
    Correct
    d

    Daniele B

    2 years ago
    Finally! I made it working:
    class AppViewModel : ViewModel() {
        val coreModel = CoreViewModel()
    }
    
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            val appViewModel: AppViewModel by viewModels()
            setContent {
                MyTextComponent(appViewModel.coreModel)
            }
        }
    }
    
    @Composable
    @ExperimentalCoroutinesApi
    fun MyTextComponent(coreModel: CoreViewModel) {
        val state by coreModel.stateFlow.collectAsState()
        Text(text = state.mytext)
    }
    So, now I am using the Android framework ViewModel, where I am wrapping the core view model
    @Ian Lake thanks for your patience!
    i

    Ian Lake

    2 years ago
    Great, I'm glad you got it working!
    Michal Harakal

    Michal Harakal

    2 years ago
    @Daniele B I've came to the same conslusions, using Android's ViewModel, but comming from multiplatform corner, where I wanted to get also viewmodel sharable with other platforms. And thanks actual/expect its perfectly doable without wrapper- Obsesed with "don't repetead yourself" I have also learned that its good to go with platform instead of fighting it.
    d

    Daniele B

    2 years ago
    yes, actually it’s just a thin wrapper. So it’s not a big deal, and the Android’s ViewModel is also the suitable place where to trigger app events, such as entering backround/foreground
    galex

    galex

    2 years ago
    @Daniele B You can also inherit from Android ViewModel for your actual class, here’s the common one:
    expect open class ViewModel() {
    
        protected val viewModelScope: CoroutineScope
    
        open fun onCleared()
    }
    And here’s the Android one:
    actual open class ViewModel actual constructor() : ViewModel() {
    
        protected actual val viewModelScope: CoroutineScope = CoroutineScope(Dispatchers.Main)
    
        public actual override fun onCleared() {
            super.onCleared()
    
            viewModelScope.cancel()
        }
    }
    Hope that helps!
    d

    Daniele B

    2 years ago
    @galex thanks! In my case I have a CoreViewModel class with a StateFlow holding the state. I am not sure how to integrate it with the expect/actual constructs.
    galex

    galex

    2 years ago
    Haven’t started looking at Flow and but you can do that in the expect class yes, it’s like an interface
    Maybe something like that?
    expect open class ViewModel<T>() {
        protected val state: StateFlow<T> 
        open fun onCleared()
    }