*backStack funtions not available in NavController? - android

Playing with the new fragment navigation, I wanted to dump the current backstack to the log for information purposes to learn how it all works. The problem is, when I obtain the NavController, it doesn't appear to have any of the backstack properties or functions.
Here's a simple example:
class MyFragment: Fragment() {
...
override fun onResume() {
super.onResume()
// Dump the back stack
Log.i("MyFragment", findNavController().currentBackStackEntry?.destination.label)
}
...
In Studio, currentBackStackEntry is not found. It's also true for backStackEntry and previousBackStackEntry (or using the functions explicitly). Of course, when compiling, I get an unresolved reference error.
I'm using:
Android Studio 4.1.1
Kotlin plugin 1.4.10
navigation-fragment-ktx & navigation-ui-ktx 2.3.1
I can use other functions/properties with the NavController, such as obtaining the graph. It's only the *backStack* properties that don't seem to exist.
So, the question is why aren't those properties available, yet documented (they're supposed to be there since navigation-*:2.2.0) and do others see the same issue?

Related

Adding a custom class breaks Compose compilation

I'm just trying to get started with Android development and Kotlin using Jetpack Compose. Note that, I'm a Kotlin novice, so I'm trying to learn along the way. I come from JavaScript/TypeScript background, so I'm trying to learn by thinking in JavaScript terms and implement in Kotlin terms by searching online.
I'm trying to list all installed applications on the device. While the app was working as expected up till now, I needed to add a feature to sort the installed app names. I referred to: https://www.bezkoder.com/kotlin-sort-list-objects/#Create_Class_for_handling_sorting. As soon as I added a custom class to sort the List<ApplicationInfo>, my app stopped building.
I have included my repo here: https://github.com/Hrishikesh-K/TryKotlin
If I comment these lines and this line as well, the app builds fine. With the current setup, I get an error:
Functions which invoke #Composable functions must be marked with the #Composable annotation
which points to line 21, character 18, which is the start of the word compare.
I don't understand why Compose would care about a custom class, it's not a Composable function after all. What am I missing?
In the compare method you are using LocalContext.current
override fun compare(o1 : ApplicationInfo, o2 : ApplicationInfo): Int {
return o1.loadLabel(LocalContext.current.packageManager).toString().compareTo(o2.loadLabel(LocalContext.current.packageManager).toString())
}
You can't use a #Composable functions if the method is not marked with the #Composable.
Use something different like:
data class CompareApplicationNames(val context: Context) : Comparator<ApplicationInfo> {
override fun compare(o1 : ApplicationInfo, o2 : ApplicationInfo): Int {
return o1.loadLabel(context.packageManager).toString().compareTo(o2.loadLabel(context.packageManager).toString())
}
}
Then just use:
Log.d("sorted:", listOfApplications.sortedWith(CompareApplicationNames(LocalContext.current)).toString())

How to make Fragments to be stateful in an Android Activity

I'm a newbie in Android.
I am trying to figure out how to create a stateful fragment based on MVVM with a SavedStateHandle injection.
I have created an empty project in Android Studio based on a template with bottom navigation. Then I added a text input into DashboardFragment. I want the text of this input to be saved when user switches between tabs.
At the moment I get access to VM as follows:
private val application = activity?.application
private val viewModel: DashboardViewModel by viewModels {
SavedStateViewModelFactory(application, activity as SavedStateRegistryOwner)
}
And I get an exception
ava.lang.IllegalArgumentException: SavedStateProvider with the given key is already registered
Please, help me to find out what is wrong
Source code: https://github.com/ideogram-software/statefulfragments-tests/
The problem was in androidx.navigation:navigation-fragment-ktx and androidx.navigation:navigation-ui-ktx dependencies version. They were 2.3.5.
I have changed them as follows (thanks to #ianhanniballake)
implementation 'androidx.navigation:navigation-fragment-ktx:2.4.0-rc01'
implementation 'androidx.navigation:navigation-ui-ktx:2.4.0-rc01'
and the problem has gone

Why is rememberSaveable variable flagged as unused by Android Studio Arctic Fox?

Android Studio Arctic Fox (Patch 3) flags, "The value true assigned to var isVisited: Boolean in the following composable is never used":
#Composable
fun MainView(navController: NavController) {
var isVisited by rememberSaveable { mutableStateOf(false) }
if (!isVisited) {
isVisited = true // never used?
navController.navigate("NextView")
}
Button(onClick = { navController.navigate("NextView") }) {
Text(text = "MainView")
}
}
while it is clearly working as intended: to prevent MainView from navigating to NextView on subsequent visits to MainView. Here's the definition of NextView:
#Composable
fun NextView(navController: NavController) {
Button(onClick = { navController.popBackStack() }) {
Text(text = "NextView")
}
}
Is Android Studio simply not recognizing variable usage across recompositions? Or is there a more idiomatic way to execute code conditionally upon recompositions? Thanks for any answer or pointer.
I'm building for API Level 31 with Kotlin 1.5.31, Compose 1.1.0-alpha06, navigation-compose 2.4.0-alpha04, lifecycle-runtime-ktx 2.4.0-rc01, though I've seen the same behavior on API Level 30, Kotlin 1.5.21, Compose 1.0.1, navigation-compose 2.4.0-alpha04, lifecycle-runtime-ktx 2.3.1. (I'd be happy to share my MainActivity where I set up NavHost with these two views or other dependency and system info if helpful.)
2022 ANSWER:
The studio code highlighter was designed with the traditional programming model in mind, i.e., the non-declarative View System. In that, a modern Composable would be treated as just a regular function. Now inside that function, you are creating a variable, reading it in a conditional, then updating its value. Then that value is never accessed in the scope of that function, and hence the warning "The updated value is never used".
However, we being Compose-Oriented now, know that it is a declarative paradigm and that recompositions are a thing, by the virtue of which, the function would be called over and over again and hence the updated value will, in fact, be used within that same conditional, which is why the rememberSaveable block is there to begin with. The same goes for the compiler, since you never "type the name of the variable" in the function scope after updating it, looking from the traditional way, the value is never used again, so the update is pointless to the compiler (as well as the highlighter), and hence the warning. It only makes sense to ignore it, but if you are really bugged by it, just type the name of the variable right after the whole loop, it should go away. God I was so dumb a year ago...
WRITTEN BY 2021 ME; NOT AS RELIABLE. FEEL FREE TO IGNORE
TOP OF THE LINE: You can safely ignore that warning, because the purpose of warnings is to prevent developers from using unnecessary system resources. Because of some internal highlighter logic error (perhaps?), it identifies a useful variable as an unused one, but since you know you are actively using it in code flow, you can just ditch that warning. Just suppress it with an annotation if it bugs you.
BODY CONTENT:-
Don't bother, it happens at times. There seems no error to me, just try deleting the line, and then re-adding it while using as much code completion as you can. If that does not help, just compile the project, and see the build log. If it does not show the warning: var 'isVisited' is never used, then you can relax about it, since it would then be a bug in the studio's code highlighter. Long as you don't receive any build time warnings, be sure that it does not pose any threat pertaining to performance or whatsoever.

Implementing Custom Back Navigation in Android

I am trying to implement custom back navigation in my fragment based on the documentation here
But when I try to replace the "// Handle the back button event" bit with my back button evet code, my IDE throws up an error saying "Required: OnBackPressedCallback found () -> OnBackPressedCallback"
I am using appcompat:1.0.2. and I have core-ktx:1.0.2'.
implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'androidx.core:core-ktx:1.0.2'
Here is how I am trying to implement the onBackPressedDispatcher
val callback = requireActivity().onBackPressedDispatcher.addCallback(this) {
patientsActivity.moveToLoginScreen(true) // <-- thing I want to happen when the back button is pressed
}
What am I missing here? How do I go about implementing custom back navigation?
The problem with the code is not on the gradle files but on the syntax. The documentation does not provide the proper kotlin code that corresponds to the java code that was given.
Here is the propper way of creating an object of an abstract class - OnBackPressedCallback
val callback = requireActivity().onBackPressedDispatcher.addCallback(object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
patientsActivity.moveToLoginScreen(true)
}
})
support a change in the documentation here: https://issuetracker.google.com/issues/135469965
OnBackPressedDispatcher is part of the new androidx.activity dependency. The best way to gain access to the new library is to upgrade your version of Fragments:
implementation "androidx.fragment:fragment-ktx:1.1.0-beta01"
By using the fragment-ktx artifact, you also will gain a transitive dependency on activity-ktx, which is what has the Kotlin extension that the documentation uses.
Specifically, you need import androidx.activity.addCallback and in your app build.gradle, implementation "androidx.fragment:fragment-ktx:1.1.0-beta01"
You haven't passed the boolean argument and that is why it doesn't work. The right code is as shown in Jian Astrero's answer. Alternatively, you can use the lambda syntax like this:
val callback = requireActivity().onBackPressedDispatcher.addCallback(this, true) {
patientsActivity.moveToLoginScreen(true)
}
Note that the true passed as second parameter is essential for it to work.

Kotlin annotation processing ignores items with similar names

I recently converted the majority of my project to kotlin. Now I encounter several unusual errors that all seem to relate to annotation libraries. Needless to say, it didn't happen in Java.
I'll describe the cases - one in Dagger and one in Butterknife.
1.
When having 2 #Provides methods in different models with the same name.
For example in file "FooProvider.kt" having a "provideFooOrBar" method
#Module
class FooProvider(private val view: FooActivity) {
...
#Provides #FooScope fun provideView() = view
#Provides #FooScope fun provideFooOrBar() = Foo()
}
And having another file "BarProvider.kt" with the same method name
#Module
class BarProvider(private val view: BarActivity) {
...
#Provides #BarScope fun provideView() = view
#Provides #BarScope fun provideFooOrBar() = Bar()
}
In this case, Dagger fails to generate some factory libraries and I get the following compilation error:
Error:(27, 32) error: cannot find symbol class FooProvider_ProvideFooOrBarFactory
A sample project reproducing the issue can be found at https://github.com/maxandron/DaggerIssue325
2.
This is an issue when using Butterknife. When having two #Bind annotated variables in two different classes - One of them just fails to initialize at runtime without any compilation error!
For example if I have:
class FooActivity {
#Bind(R.id.foo) lateinit var mFoo: View
}
class NotFooActivity {
#Bind(R.id.not_foo) lateinit var mFoo: View
}
Then one of them (or both?) will just fail to initialize without any error. Causing a kotlin.UninitializedPropertyAccessException: lateinit property mFoo has not been initialized exception to be thrown when the field is accessed.
Is it something I'm doing wrong in configuring Kotlin or is it a kotlin bug?
Thank you in advance!
Ron
I was having this issue, so I started to investigate and it's caused because Kapt is only checking the method name when comparing them, and they are added in a set, thus duplicates are not allowed. The same happens for annotated fields, so currently you can have one method/field name per annotation.
I added the class name to the equals method and the annotations were properly handled now, but the tests broke and I don't know how they work, so I hope someone knows how to fix this.
It turned out to be a bug with kapt.
I posted an issue on Kotlin's bug tracker and the issue is now marked as fixed.
This solution was merged
Should be resolved in Kotlin version 1.0.2
So to somewhat answer the kotlin.UninitializedPropertyAccessException: lateinit issue, I was running into the exact same thing in my project. What I did which "solved the issue" for me was to remove Butterknife from the offending class, in this case it was just a viewHolder for my new expandable RecyclerView, and then run the app again.
Running the app, after switching all my #Bind(R.id.my_view_id) to the "old school" findViewById(R.id.my_view_id) as MyViewType worked, but then subsequently afterwards I switched the same class back to Butterknife and the UninitializedPropertyAccessException went away, and it seems like it won't come back unless something in the class changes, then you'll have to repeat this process again.
My suspicion here is that this has something to do with Kotlin not supporting incremental compilation, and somehow by changing the auto-generated code it was forced to recompile. But I could be way off here, just thought I'd share my experience.

Categories

Resources