Update State outside the composable function. (Jetpack compose)
Asked Answered
R

4

12

I am trying to implement redux with Jetpack compose. The scenario looks like this:

I have a list view where I need to show data, in composable function.

    @Composable
    fun CreateListView(text: String) {
     val listdata = state { store.state }

        LazyColumn {
         //some listview code here
        }
    }

above, I want to use the data that I got from the redux store. but the store. The subscription method is standalone, and outside the composable. where, though I am able to update the state through new data, but the changes are not reflecting back to composable listview:

    // activity page outside composable
    private fun storeSubscription(){
        viewModel.storeSubscription = store.subscribe {

            when (store.state) {
                store.state = // list data from some source
            }
        }
    }

Is it possible to update the composable, like above, from outside the function, and without sending any parameter? Since the redux store is a global one, so it should work I think.

Ross answered 10/4, 2021 at 6:24 Comment(0)
H
1

Try something like,

@Composable
fun <T> Store<T>.asState(): State<T> {
   val result = remember { mutableStateOf(store.state) }
   DisposableEffect {
       val unsubscribe = store.subscribe {
           result.value = store.state
       }
       onDispose { unsubscribe() }
   }
   return result
}

@Composable
fun CreateListView(text: String) {
    val listdata by store.asState()

    LazyColumn {
     //some listview code here
    }
}

The exact code might differ as I don't know what redux implementation you are using.

This creates an observable state object that will be updated whenever the lambda passed to subscribe is called. Also, it will automatically unsubscribe when CreateListView is no longer part of the composition.

Homomorphism answered 16/4, 2021 at 21:51 Comment(0)
A
13

You can use MutableLiveData outside of composable function. Use observeAsState() in composable to recompose when data changes.

private val myLive = MutableLiveData<String>()

fun nonComposableScope(){
  myLive.postValue("hello")
}

@Composable
fun MyScreen(textLive:LiveData<String>){
  val text: String? by textLive.observeAsState()
  // use text here
}
Actor answered 25/11, 2021 at 8:49 Comment(0)
H
1

Try something like,

@Composable
fun <T> Store<T>.asState(): State<T> {
   val result = remember { mutableStateOf(store.state) }
   DisposableEffect {
       val unsubscribe = store.subscribe {
           result.value = store.state
       }
       onDispose { unsubscribe() }
   }
   return result
}

@Composable
fun CreateListView(text: String) {
    val listdata by store.asState()

    LazyColumn {
     //some listview code here
    }
}

The exact code might differ as I don't know what redux implementation you are using.

This creates an observable state object that will be updated whenever the lambda passed to subscribe is called. Also, it will automatically unsubscribe when CreateListView is no longer part of the composition.

Homomorphism answered 16/4, 2021 at 21:51 Comment(0)
D
1

You could simply use a lambda like so:

(An example from an app I am working on.)

@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun RumbleSearchResult(rumbleSearchResult: RumbleSearchResult, onClick: () -> Unit) {
    ListItem(
        headlineText = {
            rumbleSearchResult.title?.let { title ->
                Text(title)
            }
        },

        supportingText = {
            rumbleSearchResult.channel.let { creator ->
                val text = when {
                    rumbleSearchResult.views > 0 -> {
                        "${creator.name}, ${rumbleSearchResult.views} views"
                    }

                    else -> {
                        creator.name ?: ""
                    }
                }

                Row {
                    Text(text)

                    if (creator.isVerified) {
                        Icon(
                            painter = painterResource(R.drawable.ic_baseline_verified_24),
                            tint = Color.Cyan,
                            contentDescription = stringResource(id = R.string.mainActivity_verified_content_description)
                        )
                    }
                }
            }
        },

        leadingContent = {
            AsyncImage(
                rumbleSearchResult.thumbnailSrc,
                contentDescription = null,
                modifier = Modifier.size(100.dp, 100.dp)
            )
        },

        modifier = Modifier.clickable {
            onClick.invoke()
        }
    )
    Divider()
}

Main composable:

LazyColumn {
    items(viewModel.searchResults) {
        RumbleSearchResult(rumbleSearchResult = it) {
            openDialog = true
        }
    }
}
Desiccate answered 1/10, 2022 at 22:7 Comment(0)
P
0

You have to follow the state hosting pattern From Android Domcumentaiton

Key Term: State hoisting is a pattern of moving state up the tree to make a component stateless.

When applied to composables, this often means introducing two parameters to the composable:

value: T: the current value to display. onValueChange: (T) -> Unit: an event that requests the value to change where T is the proposed new value.

So in your case you will save the state in the upper Composable that needs to access it, and pass the value of the state and a lambda function to change it to the other Composable, you can learn more from the Official Documentation.

Phototherapy answered 10/4, 2021 at 8:42 Comment(3)
and what if my store subscription method is not in any composable method? @david-ibrahimRoss
you can pass store subscription as a lambda to your composablePhototherapy
not yet, i don't want to pass the store subscription as a parameter.Ross

© 2022 - 2024 — McMap. All rights reserved.