0

I use Jetpack Compose and have 2 screens. When I open second screen and back to the fisrt, flow variable calling again and ui updated again. But, I don't understand why... When I use liveData was working perfect.

My code with LiveData:

class MainViewModel(private val roomRepository: Repository, private val sPref:SharedPreferences) : ViewModel() {
    val words: LiveData<List<WordModel>> by lazy {
        roomRepository.getAllWords()
    }
...
}

MainScreen.kt:

@ExperimentalMaterialApi
@Composable
fun MainScreen(viewModel: MainViewModel) {
...
val words: List<WordModel> by viewModel
        .words
        .observeAsState(listOf())
...
WordList(
         words = words,
         onNoticeClick = { viewModel.onWordClick(it) },
         state = textState,
         lazyState = viewModel.listState!!
         )
...
}

@Composable
private fun WordList(
    words: List<WordModel>,
    onNoticeClick: (WordModel) -> Unit,
    state: MutableState<TextFieldValue>,
    lazyState: LazyListState
) {

    var filteredCountries: List<WordModel>
    LazyColumn(state = lazyState) {
        val searchedText = state.value.text
        filteredCountries = if (searchedText.isEmpty()) {
            words
        } else {
            words.filter {
                it.word.lowercase().contains(searchedText) || it.translation.lowercase()
                    .contains(searchedText)
            }
        }
        items(count = filteredCountries.size) { noteIndex ->
            val note = filteredCountries[noteIndex]
            Word(
                word = note,
                onWordClick = onNoticeClick
            )
        }
    }
}

WordDao.kt:

@Dao
interface WordDao {
    @Query("SELECT * FROM WordDbModel")
    fun getAll(): LiveData<List<WordDbModel>>
}

RoomRepositoryImpl.kt:

class RoomRepositoryImpl(
    private val wordDao: WordDao,
    private val noticeDao: NoticeDao,
    private val dbMapper: DbMapper
    ) : Repository {
    override fun getAllWords(): LiveData<List<WordModel>> =
        Transformations.map(wordDao.getAll()) {dbMapper.mapWords(it)}
   ...
}

DbMapperImpl.kt:

class DbMapperImpl: DbMapper {
    ...
    override fun mapWords(words: List<WordDbModel>): List<WordModel> =
        words.map { word -> mapWord(word, listOf<NoticeModel>()) }
}

My code with Flow, which calling every time when open the first screen:

class MainViewModel(private val roomRepository: Repository, private val sPref:SharedPreferences) : ViewModel() {
val words: Flow<List<WordModel>> = flow {
        emitAll(repository.getAllWords())
    }
}

MainScreen.kt:

@ExperimentalMaterialApi
@Composable
fun MainScreen(viewModel: MainViewModel) {
...
val words: List<WordModel> by viewModel
        .words
        .collectAsState(initial = listOf())
...
}

WordDao.kt:

@Dao
interface WordDao {
    @Query("SELECT * FROM WordDbModel")
    fun getAll(): Flow<List<WordDbModel>>
}

RoomRepositoryImpl.kt:

class RoomRepositoryImpl(
    private val wordDao: WordDao,
    private val noticeDao: NoticeDao,
    private val dbMapper: DbMapper
    ) : Repository {
    override fun getWords(): Flow<List<WordModel>> = wordDao.getAll().map { dbMapper.mapWords(it) }
}

And my router from MainRouting.kt:

sealed class Screen {
    object Main : Screen()
    object Notice : Screen()
    object Your : Screen()
    object Favorites : Screen()
}

object MainRouter {
    var currentScreen: Screen by mutableStateOf(Screen.Main)
    var beforeScreen: Screen? = null

    fun navigateTo(destination: Screen) {
        beforeScreen = currentScreen
        currentScreen = destination
    }
}

And MainActivity.kt:

class MainActivity : ComponentActivity() {
...
@Composable
@ExperimentalMaterialApi
private fun MainActivityScreen(viewModel: MainViewModel) {
    Surface {
        when (MainRouter.currentScreen) {
            is Screen.Main -> MainScreen(viewModel)
            is Screen.Your -> MainScreen(viewModel)
            is Screen.Favorites -> MainScreen(viewModel)
            is Screen.Notice -> NoticeScreen(viewModel = viewModel)
        }
    }
}
...
}

Perhaps someone knows why a new drawing does not occur with liveData (or, it is performed so quickly that it is not noticeable that it is), but with Flow the drawing of the list is visible.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
Alex
  • 1,407
  • 2
  • 10
  • 19
  • The question, as being mostly comprised of code is deemed too difficult to properly diagnose. Consider shredding/sorting to balance out the composition. – Richard Onslow Roper Aug 31 '22 at 18:04
  • Because, I don't know, where I did mistake, I decided show my code in more detail. Yes, it's very long, but I left only the most necessary parts. – Alex Aug 31 '22 at 18:42
  • When you navigate to another screen, the Composables are destroyed, which would always trigger a "recomposition" when navigating back to it. `LiveData` is working as expected (from what you've described in the brief summary in the beginning. The question is, why isn't `Flow`? Also, before anything, confirm the problem by switching `LiveData` and `Flow` ONLY. At times, we make multiple refactors at once, and when something breaks, it is never a good idea to blame everything on a single concept unless you have a solid hunch/understanding of the concepts involved in making the deduction. – Richard Onslow Roper Aug 31 '22 at 18:50
  • Composables are destroyed, but ViewModel is alive, and I think, val words: Flow> = flow { emitAll(repository.getAllWords())} sould't calling again. It's working for livedata: val words: LiveData> by lazy { roomRepository.getAllWords() } - executed only 1 time. – Alex Aug 31 '22 at 19:24
  • Okay, agreed. You would still be very lucky if you got a correct answer here though. Some very nerdy/nice member must review it, and if they know it, they'll answer. Most of the members would move on from questions which require finding the needle in a haystack. – Richard Onslow Roper Aug 31 '22 at 19:33
  • How exactly are you tracking recompositions? Where are you placing traces? – Richard Onslow Roper Aug 31 '22 at 19:38
  • Where/How are you initializing your ViewModel? – Richard Onslow Roper Aug 31 '22 at 19:41
  • in MainActivity: private val viewModel: MainViewModel by viewModels(factoryProducer = { MainViewModelFactory( this, (application as Application).dependencyInjector.repository, (application as Application).dependencyInjector.sPref ) }) – Alex Aug 31 '22 at 19:51
  • I added breakpoint in val words: Flow> = flow { ... HERE ... } and val words: LiveData> by lazy { ... HERE ...} – Alex Aug 31 '22 at 19:53

1 Answers1

0

You're passing the viewModel around, which is a terrible practice in a framework like Compose. The Model is like a waiter. It hangs around you, serves you water, does its job while you make the order. As you get distracted talking, it leaves. When it comes back, it is not the same waiter you had earlier. It wears the same uniform, with the same characteristics, but is still essentially a different object. When you pass the model around, it gets destroyed in the process of navigation. In case of flow, you are getting biased. Notice how you manually do a lazy initialization for the LiveData, but a standard proc. for Flow? Seems like the only logical reason for your observed inconsistency. If you want to use Flow in your calls instead of LiveData, just convert it at the site of initialization in the ViewModel. Your symptoms should go away.

Richard Onslow Roper
  • 5,477
  • 2
  • 11
  • 42
  • This approach was taken from the book "Balint T., Buketa D. - Jetpack Compose by Tutorials (1st Edition) - 2021". I think, you wrong about viewModel. You can see this approach - [author link](https://github.com/raywenderlich/jet-materials/tree/editions/1.1/08-applying-material-design-to-compose) – Alex Aug 31 '22 at 20:11
  • Mind sharing an official example of an app that uses this technique? You know, people who actually WROTE the Compose framework alongside the entire Android OS that you build for? It is one of the oldest principles in the book, known as "Separation of Concerns". Read [this question and its answers here](https://stackoverflow.com/q/71094845/15880865) – Richard Onslow Roper Aug 31 '22 at 20:18
  • [google sample](https://github.com/android/compose-samples/tree/main/Jetchat/app/src/main/java/com/example/compose/jetchat) - use viewModel with Compose. – Alex Aug 31 '22 at 20:26
  • That's a repository. you expect me to search for a line of code in tens of files just to ultimately find I was right all along? – Richard Onslow Roper Aug 31 '22 at 20:28
  • I thought you know, where use viewModel.... Okay. [direct link](https://github.com/android/compose-samples/blob/main/Jetchat/app/src/main/java/com/example/compose/jetchat/NavActivity.kt) – Alex Aug 31 '22 at 20:31
  • That even uses `Fragment`s, which are ACTIVELY discouraged by the Compose team itself. It is not a very good example now, is it? – Richard Onslow Roper Aug 31 '22 at 20:33
  • Okay, WHERE do they pass the model around? It's not done in the "direct link" you just proudly posted. – Richard Onslow Roper Aug 31 '22 at 20:34
  • Yes, but you see, it's official google samples. And If I have some coding problem, I'm reading books and looking for examples on github – Alex Aug 31 '22 at 20:35
  • [This](https://images.ctfassets.net/9l3tjzgyn9gr/photo-115842/36deea3779a0e30d3f6e5a09fa41b9a8/115842-play-with-balls.jpg?fm=jpg&fl=progressive&q=50&w=1200) is a book. – Richard Onslow Roper Aug 31 '22 at 20:37
  • I hope that's somehow relevant. – Richard Onslow Roper Aug 31 '22 at 20:37
  • Hahahah, but No. [this book](https://www.amazon.com/Jetpack-Compose-Tutorials-First-Beautiful/dp/1950325121), page 221. – Alex Aug 31 '22 at 20:41
  • Richard,maybe you know a good example on github with Compose and Flow? – Alex Aug 31 '22 at 20:50
  • You don't need `Flow`. Compose-tailored `MutableState` and `SnapshotStateList` are enough. Moreover, the book was published in the May of 2021, while `Compose 1.0` itself was released in the July of that same year, i.e., two months after the publishing. Give half a year or so to the writing and collection of resources for the book, it already makes it almost quarter to an year outdated than Compose Stable itself. If you want up-to-date resources [The Compose-Course](https://developer.android.com/courses/jetpack-compose/course) would likely do you justice. – Richard Onslow Roper Aug 31 '22 at 21:11
  • Always check the date of publishing of the tutorials/courses you take. More importantly, check the version of the libraries they are using to demonstrate the code. It is essentially important in newly released libraries/frameworks like Compose when new stuff is being added all the time. – Richard Onslow Roper Aug 31 '22 at 21:12
  • Thanks for the link. I will study the information. – Alex Sep 01 '22 at 06:02