Search code examples
androidkotlinandroid-jetpack-composecomposable

Get data from network or database on entry/load of a Jetpack Compose based screen in Android


I am new to Jetpack Compose and Kotlin, but I am trying to develop a mobile app using recently stable Jetpack Compose v1.0.0.0

I am using the NavController for navigation and my screens are made using composable functions.

Because my screen itself is a composable function, I am not able to understand how I can load some API/DB data on composable function entry.

I tried to read about LaunchedEffect, but don't know if that is the right approach and it also appeared complex to implement.

Here is the sample code:

  1. DemoScreen.kt: Consider this as a screen that is my startDestination in NavHost.

    @Composable fun DemoScreen(navController: NavController) {

     var json = "Placeholder content, to be updated on screen load and refreshed every x seconds..."
    
     //1. Make network or DB call as soon as entering this composable and store in the "json" variable
     //makeDataLoadCallOnEntry() //something like this, and show processing indicator...
    
     //2. And then keep refreshing/updating the content of the "json" variable by calling this
     //function in a timer
    
    
    
     Box(modifier = Modifier
         .fillMaxWidth()
         .fillMaxHeight()
     ){
         //region #Region: Main content area
         Column(
             horizontalAlignment = Alignment.CenterHorizontally,
             verticalArrangement = Arrangement.Center,
             modifier = Modifier
                 .fillMaxWidth()
                 .padding(horizontal = 0.dp)
             //.padding(top = 120.dp)
         ) {
    
             TextField(
                 value = json,
                 onValueChange = { json = it },
                 singleLine = false,
                 modifier = Modifier
                     .fillMaxWidth()
                     .padding(5.dp)
                     .height(500.dp),
                 maxLines = 10,
             )
             val coroutineScope = rememberCoroutineScope()
    
             Button(
                 onClick = {
                     coroutineScope.launch {
                         withContext(Dispatchers.IO) {
                             try {
                                 //do something in button click
    
                             } catch (e: Exception) {
                                 // handle exception
                                 Log.d("button", e.message.toString())
                             } finally {
                                 //do something
                                 Log.d("button", "in finally")
                             }
                         }
    
                     }
                 },
                 modifier = Modifier
                     .fillMaxWidth()
                     .padding(5.dp)
                     .height(100.dp)
             ) {
                 Text(text = "Demo Button")
             }
         }
         //endregion
     }
    

    }

  2. And my NavHost looks like this (this is working fine):

    @Composable fun NavigationMaster() { val navController = rememberNavController() val defaultScreen = Screens.DemoScreen.route

     NavHost(
         navController = navController,
         startDestination = defaultScreen){
    
         composable(
             route = Screens.DemoScreen.route
         ){
             DemoScreen(navController = navController)
         }
    
    
     }
    

    }

  3. How can I call a function on my DemoScreen composable entry to have the data from my API prior to the screen is rendered.

  4. If I can get the above, then I think I should be able to implement the timer part to automatically refresh the data from API using a loop and/or timer.

Here is a demo screenshot of how this screen currently looks (the Demo Button does not do anything, please ignore it):


Solution

  • You can do it using view model, something like this:

    @Composable
    fun DemoScreen() {
        val viewModel = viewModel<DemoScreenViewModel>()
        when (val state = viewModel.state.collectAsState().value) {
            DemoScreenViewModel.State.Loading -> {
                Text("Loading")
            }
            is DemoScreenViewModel.State.Data -> {
                DemoScreenContent(state.data)
            }
        }
    }
    
    class DemoScreenViewModel : ViewModel() {
        sealed class State {
            object Loading: State()
            data class Data(val data: String): State()
        }
    
        private var _state = MutableStateFlow<State>(State.Loading)
        val state = _state.asStateFlow()
    
        init {
            viewModelScope.launch {
                while (isActive) {
                    val data = makeDataLoadCallOnEntry()
                    _state.value = State.Data(data)
                    // wait one minute and repeat your request
                    delay(60 * 1000L)
                }
            }
        }
    
        suspend fun makeDataLoadCallOnEntry(): String {
            delay(1000)
            return "Hello world"
        }
    }