So I used https://m3.material.io/theme-builder#/custom to create a theme, I downloaded it, and I'm using it. But it only ever shows light colors.
AppTheme.kt
#Composable
fun AppTheme(
useDarkTheme: Boolean = isSystemInDarkTheme(),
content: #Composable() () -> Unit
) {
val colors = if (!useDarkTheme) {
LightColors
} else {
DarkColors
}
MaterialTheme(
colorScheme = colors,
content = content
)
}
MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onStart() {
super.onStart()
Koin.init(context = this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
AppTheme(useDarkTheme = true) {
NavHost(
navController = navController,
startDestination = Screen.Home.route
) {
addHomeTopLevel(navController)
}
}
}
}
}
HomeScreen.kt
#Composable
fun HomeScreen(
navController: NavController
) {
val viewModel: HomeViewModel by inject(HomeViewModel::class.java)
HomeContent()
}
#Composable
private fun HomeContent() {
Scaffold(
backgroundColor = MaterialTheme.colors.background,
topBar = {
TopAppBar {
Text("Hello World!")
}
},
content = {
Text("Hello Wolrd")
}
)
}
My emulator is in dark mode, but it's still showing light colors. The color values are correct, and they're part of LightColors and DarkColors correctly. The boolean is correct, and even if I change both to DarkColors it still shows light ones.
I'm not sure what I'm doing wrong here.
Related
When attempting to declare code to animate between two different composables, a 'deprecated' error is returned. How can this be fixed and cleared?
AnimatedVisibility(Boolean, Modifier = ..., EnterTransition, ExitTransition, Boolean, () -> Unit): Unit' is deprecated. AnimatedVisibility no longer accepts initiallyVisible as a parameter, please use AnimatedVisibility(MutableTransitionState, Modifier, ...) API instead
class MainActivity : ComponentActivity() {
private lateinit var navController: NavHostController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MyAppTheme {
navController = rememberNavController()
MyNavGraph(navController = navController)
}
}
}
#Composable
fun EnterAnimation(content: #Composable () -> Unit) {
AnimatedVisibility(
visible = true,
enter = slideInVertically(
initialOffsetY = { -30 }
) + expandVertically(
expandFrom = Alignment.Top
) + fadeIn(initialAlpha = 0.3f),
exit = slideOutVertically() + shrinkVertically() + fadeOut(),
content = content,
initiallyVisible = false
)
}
#Composable
fun MyNavGraph(
navController: NavHostController
) {
NavHost(
navController = navController,
startDestination = Screen.Shoes.route
) {
composable(route = Screen.Shoes.route){
ShoesScreen()
}
composable(route = Screen.Brands.route){
BrandsScreen()
}
}
}
#Composable
fun ShoesScreen() {...}
#Composable
fun BrandsScreen() {...}
}
That particular function is deprecated. You can use the new api which use MutableTransitionState instead of plain boolean.
import androidx.compose.animation.core.MutableTransitionState
#Composable
fun EnterAnimation(content: #Composable () -> Unit) {
val initialVisible = true
AnimatedVisibility(
visibleState = remember { MutableTransitionState(initialVisible) },
enter = slideInVertically(
) + expandVertically(
) + fadeIn(),
) {
content()
}
}
You can also do like this by adding AnimatedVisibilityScope to the composable type,
fun EnterAnimation(content: #Composable AnimatedVisibilityScope.() -> Unit) {
val initialVisible = true
AnimatedVisibility(
visibleState = remember { MutableTransitionState(initialVisible) },
enter = slideInVertically(
) + expandVertically(
) + fadeIn(),
content = content)
}
I am try to learning android jetpack compose, and I have simple app. In ScreenA I have a text field and when I click the button, I am save this data to firestore, and when I come in ScreenB, I want to save city name also in firebase, but I am using one viewmodel, so how can save both text field data in the same place in firestore, I did not find any solution.
ScreenA:
class ScreenAActivity : ComponentActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
setContent {
ScreenA(viewModel)
}
}
}
#Composable
fun ScreenA(
viewModel : MyViewModel
) {
val name = remember { mutableStateOf(TextFieldValue()) }
OutlinedTextField(
value = name.value,
onValueChange = { name.value = it },
label = { Text(text = "name") },
)
Button(
modifier = Modifier
.width(40.dp)
.height(20.dp),
onClick = {
focus.clearFocus(force = true)
viewModel.onSignUp(
name.value.text,
)
context.startActivity(
Intent(
context,
ScreenB::class.java
)
)
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Red
),
shape = RoundedCornerShape(60)
) {
Text(
text = "OK"
)
)
}
}
ScreenB:
class ScreenBActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
ScreenB()
}
}
}
#Composable
fun ScreenB(
) {
val city = remember { mutableStateOf(TextFieldValue()) }
OutlinedTextField(
value = city.value,
onValueChange = { city.value = it },
label = { Text(text = "city") },
)
Button(
modifier = Modifier
.width(40.dp)
.height(20.dp),
onClick = {
focus.clearFocus(force = true)
viewModel.onSignUp(
city.value.text,
)
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Red
),
shape = RoundedCornerShape(60)
) {
Text(
text = "OK"
)
)
}
}
The recommendation is use a single activity and navigate through screens using Navigation library.
After you've refactored your code to use the Navigation library, you can pass the previous back stack entry in order to get the same instance of the View Model.
val navController = rememberNavController()
NavHost(
navController = navController,
...
) {
composable("ScreenA") { backStackEntry ->
val viewModel: MyViewModel = viewModel(backStackEntry)
ScreenA(viewModel)
}
composable("ScreenB") { backStackEntry ->
val viewModel: MyViewModel = viewModel(navController.previousBackStackEntry!!)
ScreenB(viewModel)
}
}
But if you really need to do this using activity, my suggestion is define a shared object between the view models. Something like:
object SharedSignInObject {
fun signUp(name: String) {
// do something
}
// other things you need to share...
}
and in your viewmodels you can use this object...
class MyViewModel: ViewModel() {
fun signUp(name: String) {
SharedSignInObject.signUp(name)
}
}
I try to learning jetpack compose in android, so I want to use navController in a simple project, when I debug the project it throw an error as a
NullPointerException when trying to get NavController
for in this onClick = {navController.navigate("screenB")} line of code. I do not know what I missed in this project?. I was search on internet to solve it, but I am still not get it.
#AndroidEntryPoint
class NavActivity : ComponentActivity() {
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = ViewModelProvider(this)[MyViewModel::class.java]
setContent {
NavScreen()
}
}
}
#Composable
fun NavScreen() {
val viewModel = hiltViewModel<MyViewModel>()
val navController = rememberNavController()
NavHost(navController = navController, startDestination = "screenA") {
composable(route = "screenA") {
ScreenA(navController)
}
composable(route = "screenB") {
ScreenB(navController)
}
}
}
ScreenA:
#AndroidEntryPoint
class ScreenA : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
ScreenA(navController)
}
}
}
#Composable
fun ScreenA(
navController: NavController
) {
Button(
modifier = Modifier
.width(30.dp)
.height(15.dp),
onClick = {
navController.navigate("screenB")
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Red
),
shape = RoundedCornerShape(20)
) {
Text(
text = "OK",
)
}}
ScreenB:
#OptIn(ExperimentalComposeUiApi::class)
#Composable
fun ScreenB(
navController: NavController
) {
}
ScreenA should be composable not Activity. Try to leave only composable component in ScreenA
#Composable
fun ScreenA(
navController: NavController
) {
Button(
modifier = Modifier
.width(30.dp)
.height(15.dp),
onClick = {
navController.navigate("screenB")
},
colors = ButtonDefaults.buttonColors(
backgroundColor = Color.Red
),
shape = RoundedCornerShape(20)
) {
Text(
text = "OK",
)
}}
My app has a main screen with a Scaffold and a BottomNavigation bar:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
val navController = rememberNavController()
MyApplicationTheme {
Scaffold(
bottomBar = {
BottomBar(navController = navController)
}
) {
NavigationGraph(navController = navController)
}
}
}
}
...
#Composable
fun NavigationGraph(navController: NavHostController){
NavHost(navController = navController, startDestination = BottomMenuOption.Home.route) {
composable(route = BottomMenuOption.Home.route) {
HomeScreen(navController = navController)
}
composable(route = BottomMenuOption.Settings.settings) {
SettingsScreen()
}
composable(route = BottomMenuOption.Profile.route) {
ProfileScreen()
}
composable(route = "feature") {
FeatureScreen()
}
}
}
FeatureScreen has it's own Scaffold with a topBar instead of a bottomBar and when I navigate to it from HomeScreen, I want to replace the previous one from the main screen and just see a topBar but instead, I'm seeing the two bars in the screen.
#Composable
fun FeatureScreen() {
Scaffold (
topBar = {
TopBar(" Feature Screen")
}
) {
}
}
Is it possible to accomplish this? I'm thinking it could be done by just using a new Activity but ideally, I would like to keep the Single Activity approach.
I would suggest creating a new function like this:
#Composable
fun MainScaffold(
topBar: #Composable (() -> Unit) = {},
bottomBar: #Composable (() -> Unit) = {},
content: #Composable (PaddingValues) -> Unit){
Scaffold(
bottomBar = bottomBar,
topBar = topBar,
content = content
)
}
then, use this main scaffold in your screens:
#Composable
fun HomeScreen(navController: NavHostController) {
MainScaffold(
bottomBar = { BottomBar(navController = navController) },
content = {
// content
})
}
and in your feature screen:
#Composable
fun FeatureScreen() {
MainScaffold (
topBar = {
TopBar(" Feature Screen")
}
) {
//content
}
}
and in setContent
setContent {
val navController = rememberNavController()
VoiceAssistantJetpackComposeTheme {
NavigationGraph(navController = navController)
}}
I'm doing my test project using ViewModel and ComposeView.
My architecture include: one Activity and multi ComposeView, using navigation like this:
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
MainView()
}
}
}
#Composable
fun MainView() {
TestComposeTheme {
val navController = rememberNavController()
Surface(color = MaterialTheme.colors.background) {
NavHost(
navController = navController,
startDestination = KYCScreen.getName(),
) {
navigation(startDestination = KYCScreen.Preload.name, route = KYCScreen.getName()) {
composable(KYCScreen.Preload.name) { PreloadView(navHostController = navController) }
composable(KYCScreen.StartKYC.name) { StartView(navHostController = navController) }
composable(KYCScreen.Login.name) { LoginView(navHostController = navController) }
}
navigation(startDestination = HomeScreen.Home.name, route = HomeScreen.getName()) {
composable(HomeScreen.Home.name) { HomeView(navHostController = navController) }
}
}
}
}
}
The problem happens when I include logic that executes when a Composable function is executed, my logic code was looped many times. Here my code:
#Composable
fun PreloadView(navHostController: NavHostController) {
val preloadViewModel: PreloadViewModel = viewModel()
preloadViewModel.getSyncData()
val syncState by preloadViewModel.uiStateSync.observeAsState()
syncState?.let { PreloadContent(navHostController = navHostController, it) }
}
#Composable
fun PreloadContent(navHostController: NavHostController, uiState: UiState<SyncResponse>) {
when (uiState.state) {
RequestState.SUCCESS -> {
navHostController.navigate(KYCScreen.StartKYC.name)
}
RequestState.FAIL -> {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.fillMaxHeight()
.fillMaxWidth()
) {
Text(text = "Error", color = Color.Black)
}
}
RequestState.NON -> {
}
}
}
Anyone have a solution to help me with that architecture?
Your composable function should be side-effects free.
If you want to run something just once. You can do something like the following
LaunchedEffect(Unit){
preloadViewModel.getSyncData()
}
or in your case, if it is mandatory to Sync Data when ViewModel initializes you can call this function in the init block inside of your ViewModel.
Check the official doc https://developer.android.com/jetpack/compose/side-effects