I'm trying to implement a first time using screen (like any other app when you have to fill some options before using the app for the first time).
I can't go to another Jetpack compose on an main activity on-create state because it check that every recomposition, and take me to the navigation path (I'd like to check the datastore entry once during launch), this what I already try, not seem to be working:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
lifecycleScope.launch {
val onBoardingStatus = dataStoreManager.onBoard.first()
setContent {
val navController = rememberNavController()
OnBoardingNavHost(navController)
navController.navigate(if (onBoardingStatus) "on_boarding" else "main") {
launchSingleTop = true
popUpTo(0)
}
}
}
}
it is possible to check that only once (in application class for example and not in oncreate?)
please advice,
thanks in advance
You have to use LaunchedEffect for this, you can do something like this
enum class OnboardState {
Loading,
NoOnboarded,
Onboarded,
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var onboardingState by remember {
mutableStateOf(OnboardState.Loading)
}
LaunchedEffect(Unit) {
onboardingState = getOnboardingState()
}
when (onboardingState) {
OnboardState.Loading -> showSpinner()
OnboardState.NoOnboarded -> LaunchedEffect(onboardingState) {
navigateToOnboarding()
}
OnboardState.Onboarded -> showContent()
}
}
}
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 have an Activity and MyApp composable function. In the function I need to show either list with details or just list screen depending on the available width. How to determine available width for the content I want to show using Jetpack Compose? What is a good practice for this using Compose?
class MyActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ProjectTheme {
MyApp()
}
}
}
}
#Composable
fun MyApp() {
val isLarge: Boolean = ...// how to determine whether the available width is large enough?
if (isLarge) {
ListScreenWithDetails()
} else {
ListScreen()
}
}
#Composable
fun ProjectTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: #Composable () -> Unit) {
// ... project theme
}
You can use :
val configuration = LocalConfiguration.current
Then:
val isLargeWidth = configuration.screenWidthDp > 840
For a generic solution, consider using the Window Size Classes, see this and this for reference.
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val windowSizeClass = calculateWindowSizeClass(this)
MyApp(windowSizeClass)
}
}
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 a beginner to jetpack compose.
Following codelabs, I found they teach me only using top-level function.
I can use the composable function in Activity, but I can't found whoever use this way.
I wanna know which is the best practice.
my code
class RecyclerViewActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
Test()
}
}
}
#Composable
fun MyApp(content: #Composable () -> Unit) {
ComposeDemoTheme {
content()
}
}
#Composable
fun Test() {
}
}
codelabs
class RecyclerViewActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyApp {
Test()
}
}
}
}
#Composable
fun MyApp(content: #Composable () -> Unit) {
ComposeDemoTheme {
content()
}
}
#Composable
fun Test() {
}
You expected to split your code by screens/views, and each screen/view is placed inside it's own file
Usually you don't wanna have any composables in you Activity. You can set theme and other context variables right inside setContent or move it to MyApp if you have too many logic, but in this case I'd stay you move this composable into MyApp.kt too
I usually do structure like this:
screenName: package
Screen.kt
ScreenRow.kt: if screen has a list
ButtomBar.kt: if screen has buttom bar and the code is not trivial
...
So usually when you see that you file contains too many composables you move logically independent parts into other files
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")
}
}