Hi - given a login use case, where a user has to l...
# compose
m
Hi - given a login use case, where a user has to login by providing username, password and a region. The region is picked from a dropdown which is populated with values by an async api call. So I have those composables • LoginForm ◦ RegionPicker ◦ UserNameInput ◦ PasswordInput ◦ LoginButton What is best practice: having a viewmodel on LoginForm handling both the api request for regions and login (loading the regions as a
LaunchedEffect
on the LoginForm? Or having a viewmodel on the LoginForm for login and a separate viewmodel on the RegionPicker (with a
LaunchedEffect
in this sub-component)? Thread in Slack Conversation
f
Hi 🙂 I would go with only one ViewModel in the LoginForm. Your VM could make the api call to get the regions in its
init
method (if you need it right away) and then you could set them in a mutableState so that your composable will be recomposed when the data is ready. I would not load the regions using a
LaunchEffect
in my composable. Indeed, in my opinion, your composables should be as “dumb” as possible and should not know how to fetch the regions. I would rather do something like that : The viewModel
Copy code
class LoginViewModel (getRegionsUseCase: getRegionUseCase): ViewModel(){
    private val _regions: MutableStateFlow<List<String>> = MutableStateFlow(emptyList())
    val regions: StateFlow<List<String>> = _regions.asStateFlow()
    
    init {
       viewModelScope.launch  {
           _regions.value = getRegionsUseCase()
       }
    }


    fun login(username: String, password: String){
        // Do what you need to login. 
    }
}
The composable :
Copy code
@Composable
fun LoginScreen(
    viewModel :LoginViewModel = hiltViewModel()
) {
    val regions : List<String> by viewModel.regions.collectAsStateWithLifecycle()

    // Do anything you want with the regions in your composable.
}
I hope it will help you 😇
d
I'm hoping someone else can weigh in here but I've not found a great way to test such an init method :/
s
Yes, don't do it in the init. Also because if the network request fails you don't give yourself a chance to retry it. What I've done in some places is something like this https://github.com/HedvigInsurance/android/blob/05eacc685866aad298eb2f39323d8bbf40bbaeef/app/src/main/kotlin/com/hedvig/app/feature/insurance/ui/detail/coverage/CoverageViewModel.kt#L14-L41 You wrap the entire thing in a stateIn() so that you only start the flow on the first subscriber, and give them a chance to retry using this retrychannel. To test, you have the loading state to start with, then when your faked
getContractCoverageUseCase
in this case would emit, and then you get your next value. To control when that emits something, I like using Turbine.