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)
}
Related
I just learned Jetpack Compose and building a simple login screen with retrofit to connect with the API.
I'm able to navigate from login screen to home screen. But I'm wondering if I'm doing it right.
Here is my login screen composable
#OptIn(ExperimentalMaterialApi::class)
#Composable
fun InsertNumberScreen(
modifier: Modifier = Modifier,
navHostController: NavHostController,
viewModel: LoginViewModel = viewModel(factory = LoginViewModel.provideFactory(
navHostController = navHostController,
owner = LocalSavedStateRegistryOwner.current
)),
) {
var phoneNumber by remember {
mutableStateOf("")
}
var isActive by remember {
mutableStateOf(false)
}
val modalBottomSheetState =
rememberModalBottomSheetState(initialValue = ModalBottomSheetValue.Hidden)
val coroutine = rememberCoroutineScope()
ModalBottomSheetLayout(
sheetState = modalBottomSheetState,
sheetContent = {
BottomSheetLoginContent(phoneNumber){
//Here I call login function inside viewModel
viewModel.login(phoneNumber)
}
},
sheetShape = RoundedCornerShape(topStart = 16.dp, topEnd = 16.dp)
) {
Column {
TopAppBarCustom(text = "")
LoginText(modifier = modifier.padding(16.dp))
Row(modifier = modifier.padding(16.dp)) {
Prefix()
PhoneNumber(
shape = RoundedCornerShape(topEnd = 16.dp, bottomEnd = 16.dp),
value = phoneNumber,
onValueChange = {
isActive = it.length >= 10
phoneNumber = it
})
}
Spacer(
modifier = Modifier
.fillMaxHeight()
.weight(1f)
)
BottomContainer(isEnabled = isActive) {
coroutine.launch {
if (modalBottomSheetState.isVisible) {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Hidden)
} else {
modalBottomSheetState.animateTo(ModalBottomSheetValue.Expanded)
}
}
}
}
}
}
Here is my ViewModel
class LoginViewModel(val navHostController: NavHostController) : ViewModel() {
var result by mutableStateOf(Data(0, "", Message("", "")))
fun login(phone: String) {
val call: Call<Data> = Network.NetworkInterface.login(phone)
call.enqueue(
object : Callback<Data> {
override fun onResponse(call: Call<Data>, response: Response<Data>) {
if (response.code() == 400) {
val error =
Gson().fromJson(response.errorBody()!!.charStream(), Data::class.java)
result = error
navHostController.navigate("login")
} else {
result = response.body()!!
navHostController.navigate("home")
}
}
override fun onFailure(call: Call<Data>, t: Throwable) {
Log.d("Data Login", t.message.toString())
}
}
)
}
companion object {
fun provideFactory(
navHostController: NavHostController,
owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null,
): AbstractSavedStateViewModelFactory =
object : AbstractSavedStateViewModelFactory(owner, defaultArgs) {
#Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(
key: String,
modelClass: Class<T>,
handle: SavedStateHandle
): T {
return LoginViewModel(navHostController) as T
}
}
}
}
In my viewModel class, it has a constructor NavHostController. And then, in the login method, I call navHostController.navigate() to navigate to home screen if the login is success.
The question is, is it okay to call navHostController.navigate() directly inside the viewModel? Because I follow codelabs from Google and the navigation is handled in the sort of NavHostBootstrap composable (Something like this)
#Composable
fun RallyNavHost(
navController: NavHostController,
modifier: Modifier = Modifier
){
NavHost(navController = navController, startDestination = Overview.route, modifier = modifier){
composable(Overview.route){
OverviewScreen(
onClickSeeAllAccounts = {
navController.navigateSingleTopTo(Accounts.route)
},
onClickSeeAllBills = {
navController.navigateSingleTopTo(Bills.route)
},
onAccountClick = {
Log.d("Account Clicked", it)
navController.navigateToSingleAccount(it)
}
)
}
}
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.
I've read the article about handling deeplinks with Compose,
but I didn't find anything for my use case - for navigation within LazyColumn.
I have the following main Composable which I want to make the root of the graph (I want it to be host for NavHost):
#Composable
private fun MainScreenContainer(
mainState: state,
...
) {
val navController = rememberNavController()
MainTheme {
Box(
modifier = Modifier.fillMaxSize()
) {
val listState = rememberLazyListState()
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize()
) {
mainScreenContent(
state = mainState,
...
)
}
StickyToolbar(
...
)
}
}
}
and here is the remaining code:
private fun LazyListScope.mainScreenContent(
mainState: MainState,
...
) {
header(...)
overview(...)
myCard(...)
footer()
}
private fun LazyListScope.myCard(
...
) {
item {
ContentContainer(
modifier = Modifier.padding(vertical = MainTheme.spacing.medium)
) {
MyCardStateless(
onCardClick = myCardViewModel::onCardClick,
...
)
}
}
}
#Composable
fun MyCardStateless(
onCardClick: () -> Unit,
...
) {
Column(modifier = modifier) {
Card(
modifier = Modifier.fillMaxWidth()
.clickable(onClick = onCardClick)
) { ... }
}
}
And within MyCardViewModel I was using navigation to another fragment via Navigation Component:
fun onCardClick() {
navigate(
MainFragmentDirections.actionMainFragmentToDescriptionFragment()
)
}
#AndroidEntryPoint
class DescriptionFragment : ...() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
...
setContent {
DescriptionStateful(...)
}
}
...
}
But now I want to add 2 deeplinks ("myapp://mycard" - to MyCardStateless and "myapp://description" - to DescriptionFragment or to DescriptionStateful) and handle navigation using navigation-compose. Is it possible at all?
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)
}}