Giter VIP home page Giter VIP logo

Comments (11)

ahmedsalemelzeiny avatar ahmedsalemelzeiny commented on May 25, 2024

@adrielcafe @DevSrSouza any help here?

from voyager.

DevSrSouza avatar DevSrSouza commented on May 25, 2024

Can you share how you are displaying the Compose content? I mean, I want the main app Screen that you are adding the ComposeUiViewController to see if I can reproduce here.

If possible, you can share a app containing this issue that I can debug it? Maybe trying to reproduce in the sample app of Voyager and comment here the changes.

from voyager.

Shamyyoun avatar Shamyyoun commented on May 25, 2024

Hi @DevSrSouza,

There is no significant or different implementation I'm using. If you have any sample or any running app just add a functionality to open native camera UIViewController for example and then return back to our Compose UIViewController and then you will find it reloaded.

from voyager.

Shamyyoun avatar Shamyyoun commented on May 25, 2024

@DevSrSouza

This is a very quick sample app with the issue,
Just add this file in your iosMain module and render the generated MainViewController in your native iOS app code to see the issue.

Also this is a loom recording with the issue description:
(https://www.loom.com/share/8ce50514742a459396583d347e0d99b7)

Thanks in advance :)

package com.example.app

import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.interop.LocalUIViewController
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.compose.ui.window.ComposeUIViewController
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import platform.UIKit.UIImagePickerController
import platform.UIKit.UIImagePickerControllerSourceType
import platform.UIKit.UIViewController

/**
 * ================MainViewController================
 */
fun MainViewController() =  ComposeUIViewController {
    // Then render app
    Navigator(
        screen = LandingScreen
    )
}

/**
 * ================Landing Screen================
 */
internal object LandingScreen : Screen {

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

        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.White),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(
                space = 2.dp,
                alignment = Alignment.CenterVertically
            )
        ) {
            Text(
                text = "Welcome to Sample App",
                color = Color.Black,
                fontSize = 20.sp
            )

            Text(
                text = "Navigate to Create Profile",
                color = Color.White,
                fontSize = 20.sp,
                modifier = Modifier
                    .background(Color.Blue)
                    .padding(12.dp)
                    .clickable {
                        navigator?.push(
                            CreateProfileScreen
                        )
                    }
            )
        }
    }
}

/**
 * ================Create Profile Screen================
 */
internal object CreateProfileScreen : Screen {

    @Composable
    override fun Content() {
        val navigator = LocalNavigator.current
        val rootViewController = LocalUIViewController.current
        val imagePicker = remember {
            ImagePicker(
                rootController = rootViewController
            )
        }

        Column(
            modifier = Modifier
                .fillMaxSize()
                .background(Color.White),
            horizontalAlignment = Alignment.CenterHorizontally,
            verticalArrangement = Arrangement.spacedBy(
                space = 2.dp,
                alignment = Alignment.CenterVertically
            )
        ) {
            Text(
                text = "Click to Go Back",
                color = Color.White,
                fontSize = 20.sp,
                modifier = Modifier
                    .background(Color.Blue)
                    .padding(12.dp)
                    .clickable {
                        navigator?.pop()
                    }
            )

            Text(
                text = "Open Camera",
                color = Color.White,
                fontSize = 20.sp,
                modifier = Modifier
                    .background(Color.Blue)
                    .padding(12.dp)
                    .clickable {
                        imagePicker.openCamera()
                    }
            )
        }
    }
}

/**
 * ================Image Picker================
 */
internal class ImagePicker(
    private val rootController: UIViewController
) {
    private val pickerController = UIImagePickerController().apply {
        sourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypeCamera
    }

    fun openCamera() {
        rootController.presentViewController(pickerController, true, null)
    }
}

from voyager.

DevSrSouza avatar DevSrSouza commented on May 25, 2024

I have being validating your sample, thanks for sharing btw.
It seems that Compose iOS complete destroy the composition when you do a presentViewController.

Voyager relies 100% in the rememberSaveable API.

Any simple rememberSaveable is not being restored in this case.

val randomNumber = rememberSaveable {
        Random.nextInt()
    }
Text(text = "$randomNumber")

from voyager.

DevSrSouza avatar DevSrSouza commented on May 25, 2024

I have open a issue in Compose Multiplatform with your snippet.

JetBrains/compose-multiplatform#4112

from voyager.

Shamyyoun avatar Shamyyoun commented on May 25, 2024

@DevSrSouza Thanks Gabriel, hope they fix it soon

from voyager.

DevSrSouza avatar DevSrSouza commented on May 25, 2024

I have found a workaround for now.

object ComposeStateRestoration {
    var valuesToBeRestored: MutableState<Map<String, List<Any?>>> = mutableStateOf(emptyMap())
}

fun MainViewController() = ComposeUIViewController {
   CompositionLocalProvider(
        LocalSaveableStateRegistry provides SaveableStateRegistry(
            ComposeStateRestoration.valuesToBeRestored.value,
            canBeSaved = { true }
        )
    ) {
        // ... your app source code here, navigator, etc

       // last item in the code block
       val registry = LocalSaveableStateRegistry.current
       DisposableEffect(Unit) {
            onDispose {
                val savedStates = registry?.performSave()
                ComposeStateRestoration.valuesToBeRestored.value = savedStates ?: emptyMap()
            }
        }
    }
}

This solution is only for "single view controller" Compose iOS apps. If you have multiples ViewControllers and use Compose together with UiKit/SwiftUI you can change the ComposeStateRestoration to a class and add it as a parameter in the function that exposes the ComposeUiViewController (in this example MainViewController) and then, hold in the memory a instance of the ComposeStateRestoration.

from voyager.

DevSrSouza avatar DevSrSouza commented on May 25, 2024

And here is a patch validating the solution on Voyager Multiplatform sample.

diff --git a/samples/multiplatform/src/iosMain/kotlin/MainViewController.kt b/samples/multiplatform/src/iosMain/kotlin/MainViewController.kt
index dee0b39..48d1463 100644
--- a/samples/multiplatform/src/iosMain/kotlin/MainViewController.kt
+++ b/samples/multiplatform/src/iosMain/kotlin/MainViewController.kt
@@ -1,4 +1,156 @@
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
+import androidx.compose.runtime.saveable.SaveableStateRegistry
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.interop.LocalUIViewController
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.compose.ui.window.ComposeUIViewController
-import cafe.adriel.voyager.sample.multiplatform.SampleApplication
+import cafe.adriel.voyager.core.screen.Screen
+import cafe.adriel.voyager.core.screen.ScreenKey
+import cafe.adriel.voyager.core.screen.uniqueScreenKey
+import cafe.adriel.voyager.navigator.LocalNavigator
+import cafe.adriel.voyager.navigator.Navigator
+import platform.UIKit.UIImagePickerController
+import platform.UIKit.UIImagePickerControllerSourceType
+import platform.UIKit.UIViewController
 
-fun MainViewController() = ComposeUIViewController { SampleApplication() }
+object ComposeStateRestoration {
+    var valuesToBeRestored: MutableState<Map<String, List<Any?>>> = mutableStateOf(emptyMap())
+}
+
+fun MainViewController() = ComposeUIViewController {
+    CompositionLocalProvider(
+        LocalSaveableStateRegistry provides SaveableStateRegistry(
+            ComposeStateRestoration.valuesToBeRestored.value,
+            canBeSaved = { true }
+        )
+    ) {
+        val registry = LocalSaveableStateRegistry.current
+
+        Navigator(LandingScreen)
+
+        DisposableEffect(Unit) {
+            onDispose {
+                val savedStates = registry?.performSave()
+                ComposeStateRestoration.valuesToBeRestored.value = savedStates ?: emptyMap()
+            }
+        }
+    }
+}
+
+internal data object LandingScreen : Screen {
+    override val key: ScreenKey = uniqueScreenKey
+
+    @Composable
+    override fun Content() {
+        val navigator = LocalNavigator.current
+
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .background(Color.White),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(
+                space = 2.dp,
+                alignment = Alignment.CenterVertically
+            )
+        ) {
+            Text(
+                text = "Welcome to Sample App",
+                color = Color.Black,
+                fontSize = 20.sp
+            )
+
+            Text(
+                text = "Navigate to Create Profile",
+                color = Color.White,
+                fontSize = 20.sp,
+                modifier = Modifier
+                    .background(Color.Blue)
+                    .padding(12.dp)
+                    .clickable {
+                        navigator?.push(
+                            CreateProfileScreen
+                        )
+                    }
+            )
+        }
+    }
+}
+
+internal data object CreateProfileScreen : Screen {
+    override val key: ScreenKey = uniqueScreenKey
+
+    @Composable
+    override fun Content() {
+        val navigator = LocalNavigator.current
+        val rootViewController = LocalUIViewController.current
+        val imagePicker = remember {
+            ImagePicker(
+                rootController = rootViewController
+            )
+        }
+
+        Column(
+            modifier = Modifier
+                .fillMaxSize()
+                .background(Color.White),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.spacedBy(
+                space = 2.dp,
+                alignment = Alignment.CenterVertically
+            )
+        ) {
+            Text(
+                text = "Click to Go Back",
+                color = Color.White,
+                fontSize = 20.sp,
+                modifier = Modifier
+                    .background(Color.Blue)
+                    .padding(12.dp)
+                    .clickable {
+                        navigator?.pop()
+                    }
+            )
+
+            Text(
+                text = "Open Camera",
+                color = Color.White,
+                fontSize = 20.sp,
+                modifier = Modifier
+                    .background(Color.Blue)
+                    .padding(12.dp)
+                    .clickable {
+                        imagePicker.openCamera()
+                    }
+            )
+        }
+    }
+}
+
+internal class ImagePicker(
+    private val rootController: UIViewController
+) {
+    private val pickerController = UIImagePickerController().apply {
+        sourceType = UIImagePickerControllerSourceType.UIImagePickerControllerSourceTypeCamera
+    }
+
+    fun openCamera() {
+        rootController.presentViewController(pickerController, true, null)
+    }
+}

Steps the apply the code:

  • Create a file state_restauration_workaround.patch with the above patch
  • run git apply state_restauration_workaround.patch

from voyager.

DevSrSouza avatar DevSrSouza commented on May 25, 2024

This is not a Voyager issue, this workaround will do the work.
I will keep this issue open until we have proper documentation.

from voyager.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.