Should we always nullcheck Context inside a fragment? - android

I have a very simple on click listener in my fragment:
button?.setOnClickListener {
val intent = Intent(MyActivity.createIntent(context!!)) // crash here because context is null
startActivity(intent)
}
Crashlytics shows that some users are getting KotlinNullPointerException crashes when clicking on this specific button. Now I know the problem is happening because I am force unwrapping the Context. If I simply wrapped it inside a nullcheck, it would not crash.
But I assume that there's a bigger underlying issue in my code because I always force unwrap context when I need it and I only have issues with this specific piece of code.
What's the rule here? Should we always nullcheck our Context or not?

If you will look at the source code of fragment.getContext() method, you will see:
#Nullable
public Context getContext() {
return mHost == null ? null : mHost.getContext();
}
Which means that getContext can return null. Internally mHost represents an Activity fragment is attached to. Fragment isn't always attached to it's hosting activity, you can observe this using onAttach / onDetach lifecycle callbacks.
In your case, as already mentioned, best approach would be to use context from a View
view.setOnClickListener { it.context }
But in general, always check nullable things, and don't do !! even if you're sure it is not null. In such way you will have less error prone code, providing an alternative way of handling nulls.

Inside button click you can easily use : view.getContext() to get the context or in Kotlin
it.context // which will be never null
i.e,
button?.setOnClickListener {
val intent = Intent(MyActivity.createIntent(it.context)) // this wont ever crash
startActivity(intent)
}

most propably you will be okay with code like that
button?.setOnClickListener {startActivity(MyActivity.createIntent(it.context))}
I believe on your MyActivity.createIntent() fun you return Intent

Related

How to use Intent and ViewModelFactory in a fragment

I was following a tutorial by geekbygeeks they made a todo list in an activity but I want to put mine in a fragment.
So As a beginner I ran into a bunch of errors.
The first one is initializing the adapter class (of a recyclerviwer) in the Fragment.kt class
// on below line we are initializing our adapter class.
val noteRVAdapter = NoteRVAdapter(this, this, this)
I think because this refers to activities and not fragment, I get errors for the parameter this.
although if I change the first parament this to this.context it stops the error of the first parameter. (i.e)
val noteRVAdapter = NoteRVAdapter(this.context, this, this)
but I don't really know the meaning of this nor if it will work when I run the App.
Here is the NoteRVAdapter.kt file (which was initialize in the fragment.kt) :
class NoteRVAdapter(
val context: Context,
val noteClickDeleteInterface: NoteClickDeleteInterface,
val noteClickInterface: NoteClickInterface
) :
RecyclerView.Adapter<NoteRVAdapter.ViewHolder>() {
// on below line we are creating a
// variable for our all notes list.
private val allNotes = ArrayList<Note>()
....
The next Error I encountered was an Unresolved reference: application from ViewModel,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
).get(...
The application parameter of getInstance gives an Unresolved reference error even though it requires the parameter.
Here's more code of the ViewModel in the fragment.kt
// on below line we are
// initializing our view modal.
viewModal = ViewModelProvider(
this,
ViewModelProvider.AndroidViewModelFactory.getInstance(application)
).get(NoteViewModal::class.java)
This next error is on intent.
I got an unresolved reference error on #MainActivty and finish, in the code below
val intent = Intent(this#MainActivity, AddEditNoteActivity::class.java)
startActivity(intent)
this.finish()
I am not familiar with the intent but I think the this#MainActivity shouldn't be there, as I want to put this in a fragement (DashboardFragment) not an activity.
here's the whole code, the the fragment.kt file:
binding.idFAB.setOnClickListener {
// adding a click listener for fab button
// and opening a new intent to add a new note.
val intent = Intent(this#MainActivity, AddEditNoteActivity::class.java)
startActivity(intent)
this.finish()
}
And finally I got a similar error on intent here; (also in the fragment.kt file)
override fun onNoteClick(note: Note) {
// opening a new intent and passing a data to it.
val intent = Intent(this#MainActivity, AddEditNoteActivity::class.java)
intent.putExtra("noteType", "Edit")
intent.putExtra("noteTitle", note.noteTitle)
intent.putExtra("noteDescription", note.noteDescription)
intent.putExtra("noteId", note.id)
startActivity(intent)
this.finish()
}
The difference is that the finish() declared last, gives an unresolved referenceerror, and the override at the start gives an error, saying onNoteClick' overrides nothing
please by fragment.kt I mean the fragment (DashboardFragment) I want to put the todo list.
I know this is a lot, but any feedback will be greatly appreciated.. And I am more than happy to provide any other information if required.
Thanks massively for your help in advance, I honestly appreciate,
TLDR: Replace this and this#MainActivity with requireContext(). Add requireContext(). in front of startActivity() and add requireActivity(). in front of finish().
What is Context? is kind of meme among Android developers because it is so hard to explain. It's basically something you need an instance of to use many different Android classes. The weird thing is, an Activity is a Context (it's a subclass of it), but a Fragment gets attached to a Context. So when you are passing a Context parameter to a class constructor, an Activity can pass itself as this, but a Fragment has to get a reference to its attached context and pass that.
In a Fragment you can replace this with requireContext(). You usually need requireContext() instead of just context because context is nullable. They made it nullable because it is null during certain stages of the Fragment lifecycle. You need to remember not to use requireContext() in property initializers or callbacks (other than UI listeners) because property initializers are called before there is an attached context and callbacks could get fired when there is no context available and cause a crash.
When you see this#MainActivity instead of this, that's because that class named MainActivity wants to pass itself as the context, but it's doing it inside a listener interface. In that context, this would be the listener, not the Activity, so the code has to clarify which this it is referring to, which is done with # and the name of the outer class.
Once again, since you're in a Fragment, you should pass requireContext() for that parameter instead. Since it's in a click listener, which can only be called while the Fragment is attached, it's safe to use requireContext().
startActivity() is a function of Context. That's why in an Activity, you can simply call it from anywhere. In a Fragment, you need to use requireContext().startActivity().
finish() is specifically a function of an Activity so you can't call it on a Fragment. If you want to finish the Activity that contains the Fragment, you can call requireActivity().finish(). But since you're replacing your Activity with a Fragment, your structure of how you're organizing the screens of your app is different, so this may not be a one-to-one correspondence with what the original code was doing.

Observer has null object, even though the object is instantiated right above

I have this function in my app:
fun bindUI() = launch(Dispatchers.Main){
val locationResults = locationViewModel.locationResponse
val owner = viewLifecycleOwner
locationResults.observe(owner, Observer {
if (it == null) return#Observer
// TODO: set loading icon to GONE
initRecyclerView(it.features.toLocationSearchResultListItem())
})
}
This function is triggered when a certain button is pressed in the app. When debugging the code, the locationResults field is set to a RoomTrackingLiveData object, but then when it gets to the if condition inside the observer, it is null, and it returns out of the function. In this case it is of type LocationSearchResponse! and locationViewModel.locationResponse is of type LiveData<out LocationSearchResponse>
Why would it be null right after results are correctly retrieved from that variable just a few lines before?
Dispatcher.Main may be the UI thread, but the UI thread by itself does not have a view lifecycle like Activities and Fragments.
You have to bind your viewmodel within a fragment or activity or viewLifecycleOwner is going to be null.
When you think about it it makes sense that this cannot work, because you could have say 5 fragments on screen at the same time. All of them run on the UI thread with seperate lifecycles. Witch of them do you propose viewLifecycleOwner should be referring to?
Since you said this runs on a button click. Just remove the coroutine and it should probably work.
There also is a lifecycle aware coroutine scope 'viewLifecycleOwner.lifecycleScope.launch' if you wish to use a coroutine and make it lifecycle aware. But I dont really see the point here. It is possible though and maybe there is a point and somebody can enlighten me. ^^
Seems like that's not the problem like #ianhanniballake suggested your LiveData's data might be null. You can get the data with the locationResults.value or when you debug your locationResults should have a field mData. That's is later your it.

Problem to get Context in FragmentView Kotlin

I need to get a Context from my activity. When i do that using:
override fun getContext(): Context {
return activity.applicationContext
}
i got:
safe ( .) or non-null asserted ( .) calls are allowed on a nullable receiver of type FragmentACtivity
For formality purposes, posting answer here
activity is calling your fragment's getActivity() which isn't guaranteed to not be null. So you'll have to do activity!!.applicationContext!!
There are scenarios in the life cycle of Android where the activity will be null during the instance of your Fragment. More often than not, activity will exist, but in this case Kotlin is forcing you to be smart about accessing it. A simple (but helpful) nuance of Kotlin

Bad idea to override getContext() to non-null in Android api level 27?

I'm using kotlin, and recently updated my application with compileSdkVersion/targetSdkVersion 27. There I get some compilation errors, where for instance, context is now nullable(context?) instead of non-null.
I have a base fragment class, that other fragments inherits from. Is it a bad idea to have this function?
override fun getContext(): Context {
return super.getContext()!!
}
In the parent function, I see that you have this code:
/**
* Return the {#link Context} this fragment is currently associated with.
*/
#Nullable
public Context getContext() {
return mHost == null ? null : mHost.getContext();
}
Where mHost is:
// Host this fragment is attached to.
FragmentHostCallback mHost;
But the fragment will always be attached to a host, as far as I can see.
Is there any kind of scenario where mHost will be null?
Edit: In support library v27.1.0 Fragments now have requireContext(), requireActivity(), requireHost(), and requireFragmentManager() methods, which return a NonNull object of the equivalent get methods or throw an IllegalStateException.
See https://developer.android.com/reference/android/support/v4/app/Fragment.html#requireContext()
There can be situations where the fragment is not attached to a host. The simplest example is when the fragment is instantiated with its empty constructor and is not attached to anything. If you called getContext() in the constructor (or a method called from the constructor), it would result in a null value.
This means that overriding getContext() to return super.getContext() would make no sense. You might be able get rid of some null-checks but if it's null, your app will crash.
Reason being there are cases where fragments won't be attached to the parent yet but you try to access it,in such case context will be null.Best example is when you have tabs with multiple fragments and you keep switching between the tabs, most possible case is fragments will be detached and take time to attach it back but you have already switched to other tabs resulting your app to crash. So keeping context nullable is one of the best practice.

General guidelines for calling getActivity() inside DialogFragment class

As mentioned here:
"be careful to call getActivity() only when the fragment is attached to an activity. When the fragment is not yet attached, or was detached during the end of its lifecycle, getActivity() will return null."
I have a couple of questions regarding calling getActivity() inside DialogFragment.
What are different scenarios under which a DialogFragment can unexpectedly get detached from its parent Activity or didn't attach at the first place? The thing is I am calling getActivity() inside onPositiveButtonClick's listener and have received a couple of crash reports (Null pointer exception) for it. I am unable to reproduce the crash, screen orientation doesn't seem to do the trick.
What are some recommended guidelines on how to use getActivity() with minimal damage? I have read some other stackoverflow posts which suggest
a) Override onAttach() method.
public void onAttach(Activity activity) {
super.onAttach(activity);
mActivity = activity;
}
I would prefer this less as it keeps an instance of the Activity. Also, how can I be sure local instance of the activity is never set to null. Would like to know the pros and cons before using it.
b) Will delegating the onClick() implementations to the calling activity using an interface help? If yes, how?
If all of this is unavoidable, I don't see a better alternative than letting the app crash. I can't show a toast since getActivity() is null and would avoid letting the onClick operation fail silently.
Any pointers would be much appreciated. Thank you!

Categories

Resources