I set up my code like this, with a viewModel which...
# compose
d
I set up my code like this, with a viewModel which is platform-independent:
Copy 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)
}
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?
h
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
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?
h
sorry for jumping the gun then 🙂 Yes, extending on Android's ViewModel API would be better.
d
I was expecting that on Compose, we just need to work with data, and not with Android’s ViewModel
h
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
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
Copy code
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:
Copy code
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
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
😮 5
d
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:
Copy 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
Yeah, really looks like you should be using Android ViewModels
d
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
MVVM is usually built in top of a repository layer, yes.
d
@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
You should use an Android ViewModel if you're building objects outside of a
@Composable
that needs to be retained across config changes
👍 1
d
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
Correct
d
Finally! I made it working:
Copy code
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
Great, I'm glad you got it working!
m
@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
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
g
@Daniele B You can also inherit from Android ViewModel for your actual class, here’s the common one:
Copy code
expect open class ViewModel() {

    protected val viewModelScope: CoroutineScope

    open fun onCleared()
}
And here’s the Android one:
Copy code
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
@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.
g
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?
Copy code
expect open class ViewModel<T>() {
    protected val state: StateFlow<T> 
    open fun onCleared()
}