adrielcafe / voyager

🛸 A pragmatic navigation library for Jetpack Compose
https://voyager.adriel.cafe
MIT License
2.48k stars 124 forks source link

Pass data between screens #39

Open Syer10 opened 2 years ago

Syer10 commented 2 years ago

My application has a case where it needs to pass a variable to a previous screen from the current screen. With fragments I have been using setFragmentResultListener api to do this, but with Voyager there is not a way to properly handle cases like this.

My suggestions:

aurimas-zarskis commented 2 years ago

I am also interested in such functionality

JohnBuhanan commented 2 years ago

I'm trying to figure out best way to do this too.

JohnBuhanan commented 2 years ago

I think about this every week or so.

Any updates?

adrielcafe commented 2 years ago

I'm focusing on fixing the known bugs to release a stable 1.0.0. After that will start working on new features like this one.

beyondeye commented 2 years ago

I also have this problem. I have a solution that seems working, even though I am not 100% that what I am doing is correct. The point is that kotlin lambdas are serializable by default, so I thought that we could simply when, in the first screen, we create the second screen we simply pass a callback as an argument of the second screen class, to be called to pass back the result.

so basically something like this

class FirstScreenModel(var theResultFromScreen2:Long=0):ScreenModel

class FirstScreen(): Screen {
    @Composable
    override fun Content() {
        val screenModel = rememberScreenModel { FirstScreenModel() }
        //... the code for defining UI of FirstScreen
        val navigator = LocalNavigator.currentOrThrow
        Column {
            Text("First Screen")
            TextButton(onClick = { navigator.push(SecondScreen({ screenModel.theResultFromScreen2 = it })) }) {
                Text("click here to open second screen")
            }
            Text("result from second screen ${screenModel.theResultFromScreen2}")
        }
    }
}

class SecondScreen(val onResult:(Long)->Unit): Screen {
    @Composable
    override fun Content() {

        val navigator = LocalNavigator.currentOrThrow
        //update result for caller screen
        onResult(System.currentTimeMillis())
        Column {
            Text("Second Screen")
            TextButton(onClick = { navigator.pop() }) {
                Text("click here to return to previous screen")
            }
        }

    }
}

I am still a beginner with Android Compose so I am not 100% if what I am doing could potential behave not as expected in some cases. But at least apparently it is working

DjakaTechnology commented 2 years ago

@beyondeye I think it will crash the app. Try to open your screen, minimize (press home screen) then open your app. You should see crashes caused by `java.lang.RuntimeException: Parcelable encountered IOException writing serializable object

beyondeye commented 2 years ago

@DjakaTechnology you are right! @Syer10
Hi , I have just published a fork of voyager with bug fixes and new features here. I have integrated voyager with a port of flutter_bloc library that make passing data between screen very easy. (Screen can share data through shared blocs). I will be happy to have your feedback.

terrakok commented 11 months ago

I made PoC how you can pass a data between screens and don't care about the process death:

@Composable
internal fun App(
    systemAppearance: (isLight: Boolean) -> Unit = {}
) = AppTheme(systemAppearance) {
    Navigator(ScreenA())
}

interface AppScreen : Screen {
    fun <T> onResult(obj: T) {}
}

fun <T> Navigator.popWithResult(obj: T) {
    val prev = if (items.size < 2) null else items[items.size - 2] as? AppScreen
    prev?.onResult(obj)
    pop()
}

class ScreenA : AppScreen {
    var txt = "Init text"

    override fun <T> onResult(obj: T) {
        txt = obj as String
    }

    @Composable
    override fun Content() {
        val navigator = LocalNavigator.currentOrThrow

        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            Text(
                text = txt,
                style = MaterialTheme.typography.headlineMedium,
                modifier = Modifier.padding(16.dp)
            )
            Button(
                onClick = {
                    navigator.push(ScreenB())
                },
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            ) {
                Text("Enter text")
            }
        }
    }
}

class ScreenB : Screen {
    @Composable
    override fun Content() {
        val navigator = LocalNavigator.currentOrThrow
        Column(
            modifier = Modifier.fillMaxSize(),
            verticalArrangement = Arrangement.Center,
            horizontalAlignment = Alignment.CenterHorizontally
        ) {
            var txt by rememberSaveable { mutableStateOf("") }
            OutlinedTextField(
                value = txt,
                onValueChange = { txt = it },
                modifier = Modifier.padding(16.dp)
            )
            Button(
                onClick = {
                    navigator.popWithResult(txt)
                },
                modifier = Modifier.fillMaxWidth().padding(16.dp)
            ) {
                Text("Return result")
            }
        }
    }
}
osrl commented 11 months ago

@terrakok how can I get previous screen within a bottomsheet screen? items.size is 1 in that bottomsheet obviously.

terrakok commented 11 months ago

Take a parent

osrl commented 11 months ago

I'm sorry I couldn't understand what you mean by that.

osrl commented 11 months ago

navigator?.parent is null if you meant that.

AlexanderKatonaGetNetEurope commented 8 months ago

Hello, any updates on result api ?

blackwiz4rd commented 2 months ago

Hello, I think this issue should be taken more in consideration because it's a basic thing for more complex apps. In addition. I am setting @Serializable as suggested in https://github.com/adrielcafe/voyager/issues/372 but the screens are still crashing :/. I will try to dig more into this. Bye!

jadhavrupesh commented 1 month ago

@blackwiz4rd did you find a solution?

DjakaTechnology commented 1 month ago

I just realized I haven't shared my solution here. This was an old project of mine so I kinda forgot

You can add Parcelize in KMM using this approach, then use it like you usually use Parcelable and Parcelize annotation. This should fix the problem.

https://stackoverflow.com/questions/70916976/how-to-use-parcelize-annotation-in-shared-module-of-kotlin-multiplatform-projec

blackwiz4rd commented 1 month ago

@jadhavrupesh @DjakaTechnology I also found a similar solution with Java Serializable (which is different from kotlinx serialization), it's also specified here: https://voyager.adriel.cafe/state-restoration/ ;)