How to call suspend function in Activity?

Viewed 1179

I am working on a project and I want to fetch the data from Api and display it in UI. I am using Jetpack Compose by the way. How can I call the suspend function in Main Activity? I want to fetch the list and then call the function bookListScreen(bookList : BookList) in LotrAppTheme{}. How can I do this?

Main Activity:

package com.example.lotrapp
 
    @AndroidEntryPoint
    class MainActivity : ComponentActivity() {
    
        private val bookViewModel: BookViewModel by viewModels()
        private lateinit var bookList : BookList
    
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
    
            setContent {
                LOTRAppTheme {
    
                }
            }
    
        }
    }
    
    @Preview(showBackground = true)
    @Composable
    fun DefaultPreview() {
        LOTRAppTheme {
    
        }
    }

View Model:

package com.example.lotrapp

import androidx.lifecycle.ViewModel
import com.example.lotrapp.models.BookList
import com.example.lotrapp.repository.BookRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject

@HiltViewModel
class BookViewModel @Inject constructor(
    private val repository: BookRepository
) : ViewModel() {

    suspend fun getBookList() : BookList? {
        return repository.getBookList()
    }
}

BookListScreen

@Composable
fun bookListScreen(bookList : BookList) {

    Column(
        modifier = Modifier
            .fillMaxSize()
            .background(Color.Gray)
    ) {

        LazyColumn(
            contentPadding = PaddingValues(all = 12.dp),
            verticalArrangement = Arrangement.spacedBy(12.dp)
        ) {
            itemsIndexed(
                items = bookList.docs
            ) {
                index, book -> BookItem(book)
            }
        }


    }
}
2 Answers

As @gtxtreme said business logic must not be in an activity but if you're doing something in activity that requires you to launch a coroutine in a composable then you can make a coroutine scope with:

val coroutineScope = rememberCoroutineScope()

Then whenever you'd like to use your suspend funtion:

coroutineScope.launch {
  //do some suspend work
  }

Or if you'd like to have a coroutine scope to run your api often depending on a specific key (such as search query in local database) then use the so-called Handlers in compose for example:

LaunchedEffect(key1 = searchQuery){
            //do api call
  }

the code between the closures will run at the very first composition and whenever the code changes.

You can get book list as soon as your view model is created using init, and store the result value in a mutable state holder: by using mutable state you're making sure your view will update when new value comes.

Check out more about state in compose documentation, including this youtube video which explains the basic principles.

@HiltViewModel
class BookViewModel @Inject constructor(
    private val repository: BookRepository
) : ViewModel() {

    var bookList by mutableStateOf<BookList?>(null)

    init {
        viewModelScope.launch {
            bookList = getBookList()
        }
    }

    private suspend fun getBookList() : BookList? {
        return repository.getBookList()
    }
}

Then you can display needed view depending on the state:

val bookList = viewModel.bookList
if (bookList != null) {
    BookListScreen(bookList)
} else {
    // loading
}
Related