Comments (11)
@adrielcafe @DevSrSouza any help here?
from voyager.
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.
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.
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.
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.
I have open a issue in Compose Multiplatform with your snippet.
JetBrains/compose-multiplatform#4112
from voyager.
@DevSrSouza Thanks Gabriel, hope they fix it soon
from voyager.
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.
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.
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)
- [Question] Compose Multiplatform for Web (Wasm) HOT 1
- Why doesn't tab options support dynamic icons?
- screenModelScope crashing when used in desktop app HOT 3
- Wasm support HOT 3
- Issue collecting info share view model
- ScreenModel not available on iOS HOT 1
- [Question] Nested navigation question
- [Android] Crash Koin injected dependencies all `null` in `ScreenModel` after updating to `1.0.0` HOT 3
- Transitions crashing after v1.0.0 migration HOT 3
- Crashing with `java.lang.IllegalStateException: no event up from DESTROYED` HOT 5
- Navigation to Screen from bottom sheet HOT 2
- java.lang.IllegalArgumentException: Key screens.ScreenD:transition was used multiple times HOT 3
- How onBackPressed handled with Desktop target. HOT 4
- multiplatform state restoration HOT 8
- ScreenModel.onDispose is not being called when exiting application HOT 1
- Linear Navigator with TabNavigator to Linear Navigator
- ScreenModel is not disposed when poped and pushed a new screen HOT 12
- How to clear all previous screen HOT 1
- Strict mode policy violation imp HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
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.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from voyager.