Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Mahendran
Mahendran

Posted on • Edited on

     

Using ViewModel-LiveData with Jetpack Compose

In this post I want to cover on where/how to make API calls on a Jetpack compose screen. In an essence the traditional UI system and and compose differs on where do we invoke the remote/async API vs how the data delivered to us. Following swim-lane diagram explains the overview of the data flow.

Compose-ViewModel

As we can see in the diagram, the ViewModel and inner layers don't differ. In other words, if you're using ViewModel with Android UI, only the UI classes will change and rest of the layers can be kept as it is.


Implementation

In compose, LiveData is consumed as state. To do so, add this dependency inbuild.gradle.

// https://maven.google.com/web/index.html?q=livedata#androidx.compose.runtime:runtime-livedataimplementation"androidx.compose.runtime:runtime-livedata:$compose_version"
Enter fullscreen modeExit fullscreen mode

One-off call

In the composable function, observe the data as state usingLiveData#observeAsState extension.

Next, make API call usingLaunchedEffect - for one time call, useUnit or any constant as key.

For UI, as usual - skim through the data and construct the UI. This example shows listing books.

@ComposablefunBooksScreen(viewModel:BookListViewModel=hiltViewModel<BookListViewModelImpl>()){// Statevalbooks=viewModel.books.observeAsState()// API callLaunchedEffect(key1=Unit){viewModel.fetchBooks()}// UILazyColumn(modifier=modifier){items(books){// List item composableBookListItem(book=it)}}}
Enter fullscreen modeExit fullscreen mode

User triggered - API calls

In case you want to execute the LaunchedEffect block again - such as force refresh, use a variable and conditionally update thekey1 value. Remember every time when the key change, it'll invoke the API. So, keep in mind to not assign the value in render logic and put it behind user action.

@ComposablefunBooksScreen(viewModel:BookListViewModel=hiltViewModel<BookListViewModelImpl>()){// Statevalbooks=viewModel.books.observeAsState()varrefreshCountbyremember{mutableStateOf(1)}// API callLaunchedEffect(key1=refreshCount){viewModel.fetchBooks()}// UIColumn(){IconButton(onClick={refreshCounter++}){Icon(Icons.Outlined.Refresh,"Refresh")}LazyColumn(modifier=modifier){items(books){// List item composableBookListItem(book=it)}}}}
Enter fullscreen modeExit fullscreen mode

ViewModel implementation

ViewModel is an interface contract which exposes data through LiveData and has helper functions to carry out actions.

interfaceBookListViewModel{// Datavalbooks:LiveData<List<Book>>// OperationsfunfetchBooks()}
Enter fullscreen modeExit fullscreen mode

The consuming classes shall refer the interface and the actual implementation will be an Android ViewModel. Wiring of this implementation to UI classes will be taken care by dependency injection.

Internally, the viewmodel implementation overrides the data variables to provide actual data. As for the operations, theviewModelScope ensures the API call lives within viewmodel's lifetime, and launches the remote operation.

Remember viewModelScope still executes in Main thread. Offloading the task to IO happens in repository layer.

classBookListViewModelImpl(privatevalrepo:BooksRepository):BookListViewModel{privateval_books=MutableLiveData<List<Book>>()overridevalbooks:LiveData<List<Book>>get()=_booksoverridefunfetchBooks(){viewModelScope.launch{_books.value=repo.fetchBooks()}}}
Enter fullscreen modeExit fullscreen mode

Repo implementation

Repo executes a long running operation. In kotlin world, it is a suspend function runs in IO dispatcher context.

classBooksRepository{suspendfunfetchBooks():List<Book>=withContext(Dispatchers.IO){// Some API call// Parser logicvalbooks=listOf<Book>()books}}
Enter fullscreen modeExit fullscreen mode

Top comments(6)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
karangupta2388 profile image
karangupta2388
  • Joined
• Edited on• Edited

Why is LaunchEffect even needed inside the composable? We are already using viewModelScope.launch to fetch the book which should prevent any leaks right?
A response would help as I am confused between viewModelScope.launch vs LaunchEffect.

CollapseExpand
 
mahendranv profile image
Mahendran
Android / Kotlin / Python / KMP
  • Location
    India
  • Work
    Senior android developer at Omnissa
  • Joined

LaunchEffect is a trigger to load content upon first composition (provided proper key).

viewModelScope.launch will scope the coroutine to retained-activity (i-e survives config changes). It is independent of the whether we use traditional xml UI or compose.

CollapseExpand
 
jamescodingnow profile image
James
#Dev #iOS #Android #Swift #Kotlin 💻 #Science Lover 🧬 Data Structures and Algorithms #DSA 📚 Weekly content 📱 Current targets are #SwiftUI ✨ #JetpackCompose 🚀
  • Education
    University
  • Work
    Android Developer
  • Joined

Can you share your view model?

CollapseExpand
 
mahendranv profile image
Mahendran
Android / Kotlin / Python / KMP
  • Location
    India
  • Work
    Senior android developer at Omnissa
  • Joined
• Edited on• Edited

Updated the post to include repo and viewmodel implementation.

I don't have exact implementation in hand. But, here is the rough layout (minus repository / data source)

importandroidx.lifecycle.LiveDataimportandroidx.lifecycle.MutableLiveDataimportkotlinx.coroutines.launchclassBook{varname:String=""}interfaceBookListViewModel{varbooks:LiveData<List<Book>>funfetchBooks()}classBookListViewModelImpl:BookListViewModel{privateval_books=MutableLiveData<List<Book>>()overridevarbooks:LiveData<List<Book>>get()=_booksset(value){// no-op}overridefunfetchBooks(){viewModelScope.launch{// _books.value = someLongOperationInRepo()}}}
Enter fullscreen modeExit fullscreen mode
CollapseExpand
 
kururu95 profile image
kururu
A professional Mobile Developer

but your using HiltViewModel in compose function

how this is possible?

Thread Thread
 
mahendranv profile image
Mahendran
Android / Kotlin / Python / KMP
  • Location
    India
  • Work
    Senior android developer at Omnissa
  • Joined

It is supported. Just like theviewModel() delegate. Please check this documentation:developer.android.com/develop/ui/c...

Oddly it is placed under hilt-navigation.

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Android / Kotlin / Python / KMP
  • Location
    India
  • Work
    Senior android developer at Omnissa
  • Joined

More fromMahendran

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp