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
Related
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 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()
}
}
}
I have run into an issue with Jetpack Compose. In my application's old classes (activity and fragment) I am using a custom Context wrapper for my custom Resources wrapper. This is because string resources values are downloaded from a server; the Strings resource files do contain string IDs, their values are empty and are replaced by the server's values.
In activities and fragments I can simply replace the context in getContext() or attachBaseContext(newBase: Context?) methods, but how do I do this in Jetpack Compose?
Jetpack Compose does have these Context methods: LocalConfiguration.current and LocalContext.current. I love how straightforward this is, but I was not able to find where this Context object is inited. At first, I thought it was in the parent Activity in attachBaseContext() method when I set content, but this code does not seem to be useful:
#AndroidEntryPoint
class MyComposeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
MyComposeActivityScreen()
}
}
}
override fun attachBaseContext(newBase: Context?) {
val language = Locale(Utils.getLanguage(newBase))
super.attachBaseContext(MyCustomContextWrapper.wrap(MyCustomResourcesContextWrapper(newBase), language))
}
You can try specifying the context manually with CompositionLocalProvider:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateComposeView(this)
}
override fun attachBaseContext(newBase: Context?) {
super.attachBaseContext(newBase)
updateComposeView(newBase ?: this)
}
fun updateComposeView(context: Context) {
setContent {
CompositionLocalProvider(
LocalContext provides context
) {
ProvideWindowInsets(windowInsetsAnimationsEnabled = true) {
MyComposeActivityScreen()
}
}
}
}
Also a more Compose way to solve the original problem is to create your own string holder, something like LocalStrings, kind of as shown in this answer.
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")
}
}
I used jetpack compose AppBarIcon element but I got the error:
Functions which invoke #Composable functions must be marked with the #Composable"
when I call a composable function on the onclick lamba. This is the code:
class testActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent { AppBarIcon(Image(10, 10) ) {
composableFunction()
}
}
}
}
#Composable()
fun composableFunction() {
}
and it is a real problem becaus I got an exception at run-time
It is somethink I miss or it is a real bug ?
First thing to note that Composable function must only be called inside another Composable function.
Now back to your question, onClick parameter which accept the function is not a composable function. So calling #Composable function inside a onClick isn't the option and hence throw an error Functions which invoke #Composable functions must be marked with the #Composable.
Code to resolve this issue.
class testActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
var loadView = +state { false }
Column {
AppBarIcon(+imageResource(R.drawable.dashboard)) {
loadView.value = true
}
Text("Hey")
if (loadView.value)
composableFunction()
}
}
}
}
#Composable()
fun composableFunction() {
Text("appbar content")
}
Reason because onClick is not composable: All Composable function are called at the same time to compose the UI completely.While onClick is called later after the UI is already composed. So you need to use the state to recompose the UI and load composable function after onClick occur.
In my case, I added a wrong import of Composable:
import androidx.compose.Composable
The issue went away after I imported runtime.Composable instead.
import androidx.compose.runtime.Composable