I've a requirement to create a navigation where the flow is completely depends on the API response. Sometimes it could be A->B->C->D->E or B->C->D->E or A->B->C->E. Already implemented the same stack management with ArrayDeque which is working fine. But want to replace with navigation compose.
I know we can change the startDestination in navigation graph. Is it possible in jetpack compose? Also, I want to make it from ViewModel.
It should be easy in Compose.
Haven't tested this, but this mostly would work.
MainActivity.kt
override fun onCreate(
savedInstanceState: Bundle?,
) {
super.onCreate(savedInstanceState)
setContent {
MyTheme {
MyApp(
finishActivity = {
finish()
},
)
}
}
}
MyApp.kt
#Composable
fun MyNavGraph(
finishActivity: () -> Unit,
) {
MyAppView(
activityViewModel = activityViewModel,
)
}
MyNavGraph.kt
#Composable
fun MyNavGraph(
activityViewModel: MainActivityViewModel,
) {
val navHostController = rememberNavController()
NavHost(
navController = navHostController,
startDestination = activityViewModel.getStartDestination(),
) {
composable(
route = Screen.Home.name,
) {
HomeScreen(
activityViewModel = activityViewModel,
)
}
// Other composables
}
}
activityViewModel.getStartDestination() would make a network call a get the starting destination.
Add a comment if there are any issues with this.
Related
I tried to load a Fragment in Compose as below, through the supportFragmentManager as shown below.
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
AndroidViewBinding(FragmentContainerBinding::inflate) {
supportFragmentManager.beginTransaction()
.replace(container.id, MyFragment()).commit()
}
}
}
}
However, when the view is shown, the fragment gets committed (loaded) several times (i.e. the onCreate() is called several times)
Any way to prevent committing several times?
Is there a way to resume the state as well (e.g. in case got killed by the system, how to get it restored)?
(note: I'm not using the androidx.fragment.app.FragmentContainerView in the XML as in Developer Doc I do have different fragments per some logic (not shown here), hence I'll have to use supportFragmentManager)
Found a way to get this working
class MainActivity : FragmentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
FragmentContainer(
modifier = Modifier.fillMaxSize(),
fragmentManager = supportFragmentManager,
commit = { add(it, MyFragment()) }
)
}
}
}
#Composable
fun FragmentContainer(
modifier: Modifier = Modifier,
fragmentManager: FragmentManager,
commit: FragmentTransaction.(containerId: Int) -> Unit
) {
val containerId by rememberSaveable { mutableStateOf(View.generateViewId()) }
AndroidView(
modifier = modifier,
factory = { context ->
fragmentManager.findFragmentById(containerId)?.view
?.also { (it.parent as? ViewGroup)?.removeView(it) }
?: FragmentContainerView(context)
.apply { id = containerId }
.also {
fragmentManager.commit { commit(it.id) }
}
}
)
}
Note this will need Fragment's KTX
implementation "androidx.fragment:fragment-ktx:1.4.1"
I'm using branch.io for handling deep links. Deep links can contain custom metadata in a form of JsonObject. The data can be obtained by setting up a listener, inside MainActivity#onStart() which is triggered when a link is clicked.
override fun onStart() {
super.onStart()
Branch
.sessionBuilder(this)
.withCallback { referringParams, error ->
if (error == null) {
val eventId = referringParams?.getString("id")
//Here I would like to navigate user to event screen
} else {
Timber.e(error.message)
}
}
.withData(this.intent?.data).init()
}
When I retrieve eventId from referringParams I have to navigate the user to the specific event. When I was using Navigation components with fragments I could just do:
findNavController(R.id.navHost).navigate("path to event screen")
But with compose is different because I can't use navController outside of Composable since its located in MainActivity#onCreate()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
//I cant access navController outside of composable function
val navController = rememberNavController()
NavHost(
navController = navController,
startDestination = "HomeScreen",
) {
}
}
}
My question is, how can I navigate the user to a specific screen from MainActivity#onStart() when using jetpack compose navigation
rememberNavController has pretty simple implementation: it creates NavHostController with two navigators, needed by Compose, and makes sure it's restored on configuration change.
Here's how you can do the same in your activity, outside of composable scope:
private lateinit var navController: NavHostController
private val navControllerBundleKey = "navControllerBundleKey"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
navController = NavHostController(this).apply {
navigatorProvider.addNavigator(ComposeNavigator())
navigatorProvider.addNavigator(DialogNavigator())
}
savedInstanceState
?.getBundle(navControllerBundleKey)
?.apply(navController::restoreState)
setContent {
// pass navController to NavHost
}
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBundle(navControllerBundleKey, navController.saveState())
super.onSaveInstanceState(outState)
}
I'm working on an Android app using Kotlin. The app uses fragment-based navigation but I am using some Jetpack Compose to build some elements of it instead of using RecyclerViews and such.
Right now I have a card composable that builds itself off an object and another one that creates a list of those with a LazyColumn. The card has it's own separate file but the list composable is part of the code of the fragment that uses it. This is because when one of the cards is clicked, it calls a function to load a fragment that lists the details of the object the card represents (Events in this case).
This is the code in my list fragment:
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_liste_evenement,container,false).apply {
val listeEvens : ArrayList<Événement> = ArrayList<Événement>()
listeEvens.add(évén)
listeEvens.add(évén2)
listeEvens.add(évén3)
val composeView = findViewById<ComposeView>(R.id.listeBlocsEven)
composeView.setContent {
ListeCarteÉvénements(événements = listeEvens)
}
}
}
#Composable
fun ListeCarteÉvénements(événements: List<Événement>) {
LazyColumn {
items(événements) { e ->
CarteÉvénement(événement = e,clickEvent = { loadFragment(details_evenement(e)) })
}
}
}
This is the card composable's declaration:
#Composable
fun CarteÉvénement(événement: Événement,clickEvent: () -> Unit) {
Column(modifier = Modifier
.clip(RectangleShape)
.padding(all = 8.dp)
.fillMaxWidth()
.height(300.dp)
.background(MaterialTheme.colors.primaryVariant)
.clickable(onClick = clickEvent))
private fun loadFragment(fragment: Fragment) {
val transaction = requireActivity().supportFragmentManager.beginTransaction()
transaction.replace(R.id.fragmentContainerView, fragment)
transaction.addToBackStack(null)
transaction.commit()
}
As you can see, doing it this way allows me to get direct access to the event cards so that I can give my details fragment the clicked event as an attribute.
This all works but my question is: If I wanted to put the list composable in the same file as the card(outside of the fragment), how would I pass it the loadFragment function that receives a fragment that also has it's own parameter(in this case the event from the clicked card)?
You can pass a lambda callback to ListeCarteÉvénements which receives the event as an argument.
override fun onCreateView (...) : View {
...
composeView.setContent {
ListeCarteÉvénements(
événements = listeEvens,
onItemClick = { e -> loadFragment(details_evenement(e)) }
)
}
...
}
#Composable
fun ListeCarteÉvénements(événements: List<Événement>, onItemClick: (Événement) -> Unit {
LazyColumn {
items(événements) { e ->
CarteÉvénement(événement = e,clickEvent = { onItemClick(e) })
}
}
}
I just started with Jetpack Compose. My app has many screens, it shows StatusBar with color, which is defined in the theme, but on some screens, I want to make StatusBar color is transparent and Image show under StatusBar.
Please guide me step by step.
Thank you in advance.
Just write this line before setContent
window.setFlags(
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
)
Firstly in your res > theme remove action bar;
<style name="Theme.app_android"
parent="Theme.MaterialComponents.Light.NoActionBar">
Then call your composable in MainActivity, setting WindowCompat before as shown below
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
WindowCompat.setDecorFitsSystemWindows(window, false)
setContent {
App()
}
}
}
Then using the ProvideWindowInsets function to remember a systemUiController that's called inside a SideEffect function that sets the system status bar to transparent
#Composable
fun App() {
AppTheme {
ProvideWindowInsets {
val systemUiController = rememberSystemUiController()
SideEffect {
systemUiController.setSystemBarsColor(Color.Transparent,
darkIcons = false)
}
val navController = rememberNavController()
val coroutineScope = rememberCoroutineScope()
val navBackStackEntry by
navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
Scaffold() { innerPadding ->
Box(modifier = Modifier.padding(innerPadding)) {
NavGraph(navController = navController)
}
}
}
}
}
If you clone the compose samples from here https://github.com/android/compose-samples they show you this way to implement it
Before setContent{} on Activity onCreate() do:
WindowCompat.setDecorFitsSystemWindows(window, false)
I use the navigation of official example OwlApp as my main navigation system, and I want to achieve the camerax in my project. But there is no easy way to combine camera in compose, so I think if have a way to let a activity which have a camera screen act as a compose destination, then I can navigate it normally as the app does.
You could handle this by passing a lambda into the composable function and implementing the logic outside of the composable function (inside your activity).
class MainActivity : AppCompatActivity() {
lateinit var onCameraClick : () -> Unit
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
SampleTheme {
Surface(color = MaterialTheme.colors.background) {
MainView(onCameraClick = onCameraClick)
}
}
}
onCameraClick = {
// Camera logic inside activity scope.
}
}
}
#Composable
fun MainView(onCameraClick : () -> Unit) {
Button(onClick = onCameraClick) {
Text(text = "Camera")
}
}