I was watching this tutorial and I was wondering if there's a way to send parameters from the App to a Voyager Screen and if so, how? Or to somehow combine this ImagePicker
implementation with bottom navigation?
I've tried many things but nothing actually worked, I've either ended up with a blank screen or a crash that said
java.lang.RuntimeException: Parcelable encountered IOException writing serializable object
Code:
actual class ImagePickerFactory {
@Composable
actual fun createPicker(): ImagePicker {
val activity = LocalContext.current as ComponentActivity
return remember(activity) { ImagePicker(activity) }
}
}
actual class ImagePicker(private val activity: ComponentActivity) {
private lateinit var getContent: ActivityResultLauncher<String>
@Composable
actual fun RegisterPicker(onImagePicked: (ByteArray) -> Unit) {
getContent =
rememberLauncherForActivityResult(ActivityResultContracts.GetContent()) { uri ->
uri?.let {
activity.contentResolver.openInputStream(uri)?.use {
onImagePicked(it.readBytes())
}
}
}
}
actual fun ShowImagePicker() {
getContent.launch("image/*")
}
}
These are my image picker classes on android side. Shared code looks like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
App(ImagePickerFactory().createPicker())
}
}
}
@Composable
@Preview
fun App(imagePicker: ImagePicker) {
initKoin()
MaterialTheme {
TabNavigator(
tab = HomeTab
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
bottomBar = {
BottomNavigation(
backgroundColor = Color(0xFF3F54BE)
) {
TabNavigationItem(HomeTab)
TabNavigationItem(SearchTab)
TabNavigationItem(ProfileTab(imagePicker))
}
},
content = { CurrentTab() }
)
}
}
}
@Composable
private fun RowScope.TabNavigationItem(tab: Tab) {
val tabNavigator: TabNavigator = LocalTabNavigator.current
BottomNavigationItem(
modifier = Modifier.padding(top = 24.dp),
selected = tabNavigator.current == tab,
onClick = { tabNavigator.current = tab },
icon = {
tab.options.icon?.let { icon ->
Icon(
painter = icon,
contentDescription = null,
tint = if (tabNavigator.current == tab) Color.White else Color.Gray
)
}
},
label = {
Text(text = tab.options.title)
}
)
}
data class ProfileTab(private val imagePicker: ImagePicker) : Tab {
@Composable
override fun Content() {
Navigator(screen = ProfileScreen(imagePicker)) { navigator ->
SlideTransition(navigator = navigator)
}
}
override val options: TabOptions
@Composable
get() {
val index: UShort = 2u
return TabOptions(
icon = rememberVectorPainter(Icons.Default.Person),
index = index,
title = ""
)
}
}
class ProfileScreen(private val imagePicker: ImagePicker) : Screen {
@Composable
override fun Content() {
val viewModel = getScreenModel<ProfileViewModel>()
imagePicker.RegisterPicker { imageBytes -> viewModel.saveUserImage(imageBytes) }
ProfileScreenRoot(viewModel) { imagePicker.ShowImagePicker() }
}
}
@Composable
fun ProfileScreenRoot(viewModel: ProfileViewModel, onImagePicked: () -> Unit) {
//code unrelated to this issue
}
I've end up "fixing" this by replacing the ImagePicker with this cool library that is used for both Android and iOS called peekaboo. It's pretty simple and neat, I recommend using it instead of implementing image picker separately on both platforms, because it's much easier and you won't get into issues like needing to pass context on android side for creating the image picker factory, passing it to the screens from App, etc.