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.
Related
I am trying to create a getInstance function inside a companion object in a Fragment class called "ListFragment" which returns an instance of ListFragment. I am trying to get the instance of this fragment from a viewModel class which is being used in the same fragment. By doing this, I am getting following error:
java.lang.IllegalStateException: Fragment ListFragment{b13aa81} (7e6b48f5-7ae5-4b8b-b19f-13b3df28c66c) not attached to an activity.
at androidx.fragment.app.Fragment.requireActivity(Fragment.java:995)
at in.machine_test.arun.viewmodels.ListFragmentViewModel.showToast(ListFragmentViewModel.kt:106)
at in.machine_test.arun.viewmodels.ListFragmentViewModel$insert$1$2.emit(ListFragmentViewModel.kt:60)
at in.machine_test.arun.viewmodels.ListFragmentViewModel$insert$1$2.emit(ListFragmentViewModel.kt:59)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt$catchImpl$2.emit(Errors.kt:158)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
at kotlinx.coroutines.flow.internal.SafeCollectorKt$emitFun$1.invoke(SafeCollector.kt:15)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:87)
at kotlinx.coroutines.flow.internal.SafeCollector.emit(SafeCollector.kt:66)
at in.machine_test.arun.data.repository.LocalRepository$insert$1.invokeSuspend(LocalRepository.kt:24)
at in.machine_test.arun.data.repository.LocalRepository$insert$1.invoke(Unknown Source:8)
at in.machine_test.arun.data.repository.LocalRepository$insert$1.invoke(Unknown Source:4)
at kotlinx.coroutines.flow.SafeFlow.collectSafely(Builders.kt:61)
at kotlinx.coroutines.flow.AbstractFlow.collect(Flow.kt:230)
at kotlinx.coroutines.flow.internal.ChannelFlowOperatorImpl.flowCollect(ChannelFlow.kt:195)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collect$suspendImpl(ChannelFlow.kt:167)
at kotlinx.coroutines.flow.internal.ChannelFlowOperator.collect(Unknown Source:0)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt.catchImpl(Errors.kt:156)
at kotlinx.coroutines.flow.FlowKt.catchImpl(Unknown Source:1)
at kotlinx.coroutines.flow.FlowKt__ErrorsKt$catch$$inlined$unsafeFlow$1.collect(SafeCollector.common.kt:113)
at in.machine_test.arun.viewmodels.ListFragmentViewModel$insert$1.invokeSuspend(ListFragmentViewModel.kt:59)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
at kotlinx.coroutines.internal.LimitedDispatcher.run(LimitedDispatcher.kt:42)
at kotlinx.coroutines.scheduling.TaskImpl.run(Tasks.kt:95)
at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:570)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:749)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:677)
at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:664)
Suppressed: kotlinx.coroutines.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}#66b95d6, Dispatchers.IO]
Below is the code which I have written in LoginFragment.
companion object {
#JvmStatic
fun getInstance(): ListFragment = ListFragment()
}
Trying to get the instance from ViewModel
private fun showToast(message: String) {
ListFragment.getInstance().requireActivity().runOnUiThread {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
}
}
To get the instance, I know there are other ways like passing the instance of this class in viewModel constructor or use a dependency injection.
But I am wondering how to get an instance using this getInstance approach.
Any help will be greatly appreciated.
The error's telling you the problem:
java.lang.IllegalStateException: Fragment ListFragment{b13aa81} ...
not attached to an activity.
You're getting this not attached to an activity error because you're trying to use this Fragment that you've just created to get access to an Activity
ListFragment.getInstance().requireActivity()
And that's not how Android works. Usually when you have access to a Fragment it's because the system provided it, e.g. by attaching it to an Activity or ViewPager or something.
The Fragment gets created - and it's just a class, with no access to the app Context or anything - and then the system connects it to the wider app framework by attaching it to another component, which is a hierarchy that is ultimately running in an Activity (which is like a view of the app, running in a Window).
If you just do MyFragment() you're just creating that basic class - it has no connection to an Activity, because you didn't give it one. And in the context of your code, a basic function in a companion object, you can't give it one because the function doesn't have access to one.
I'm just explaining this to make it clearer what the limitations are here - I think you're already aware of them, and that's why you're trying to get access to an Activity somehow, because currently you don't have access to it. But just creating a ListFragment can't give you access you don't already have. There's no magic to it.
You basically have two options for this situation, which comes up a lot! (Especially for Contexts):
your code is running in a component that has (or usually has) access to an Activity, Context etc, like a running Fragment (i.e. in its lifecycle callbacks like onCreate), or it has a parent object it can get access through. Access it that way
your code doesn't have access, so you need to provide the Context etc, either as a parameter in a function call, or as a constructor parameter. The thing that calls the function or constructs the object needs to have access to the Context, so it can pass it in
That's what I mean about there being no magic, no tricks to gain access to things. Either you have access, or something needs to provide it. To give your ListFragment access to an Activity, you need access to that Activity yourself so you can attach the fragment to it! There's no workaround for that
As far as your specific problem goes, you just want to run your Toast call on the main thread, right? Activity#runOnUiThread is a convenience method for when you have access to an Activity - but you can do the same thing like this:
// this lets you post runnables to the message queue on the main thread
val handler = Handler(Looper.getMainLooper())
// now you can run your code on it
handler.post { Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show() }
I see you're using coroutines in your stacktrace, so maybe you just want to switch to the Main dispatcher instead:
withContext(Dispatchers.Main) {
Toast.makeText(mContext, message, Toast.LENGTH_SHORT).show()
}
Depends on what you're doing!
I started android development a few days ago. I implemented a recylerview and in the OnBindViewHolder method of the recyclerview adapter I used the setOnClickListener on the recyclerview item. My main goal was to start a new activity when the recyclerview item is clicked but I ran into a wall when implementing my code in the following way:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val clusterItem = datalist[position]
holder.clusterName.setText(clusterItem.name)
holder.clusterStrat.setText(clusterItem.strats)
holder.itemview.setOnClickListener() {
startActivity(Intent(holder.itemview.context,ClusterSearchActivity::class.java))
}
}
I had 3 errors on the line that contains startActivity:
Type mismatch: inferred type is Intent but Context was expected
No value passed for parameter 'intent'
No value passed for parameter 'options'
After going through multiple solutions I finally stumbled upon this one: https://www.titanwolf.org/Network/q/08ad14d9-cb9a-4b87-923b-f97089db769a/y
Using context.startActivity(intent) I rewrote my code like this:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val clusterItem = datalist[position]
holder.clusterName.setText(clusterItem.name)
holder.clusterStrat.setText(clusterItem.strats)
holder.itemview.setOnClickListener() {
holder.itemview.context.startActivity(Intent(holder.itemview.context,ClusterSearchActivity::class.java)) }
}
Now my code finally worked but I can't seem to understand why I had to use context.startActivity().
I'd like to understand when can I use startActivity() just like that and when I need to use context.startActivity().
First of all, Context is the current state of the application/object.
Interface to global information about an application environment.
This is an abstract class whose implementation is provided by the Android system.
It allows access to application-specific resources like colors, string resource , database access and classes, as well as up-calls for application-level operations such as launching activities, broadcasting and receiving intents, etc.
Context is the base class for Activity, Service, Application, etc
if you check inside the AppCompatActivity and Fragment.
then you can be found startActivity() method inside it.
In Your case:
In the adapter, If you need to get the database access , string res e.g: context.getResources().getString(R.string.yourstring);
needed color to set on the view on runtime , so you have to need the current state of the application/object is call context and context is a super class.
You can get access to context in three ways in the adapter.
Pass Context as an argument to the Adapter and keep it as class field.
Use dependency injection to inject Context when you need it. I strongly suggest reading about it.e.g: Android Hilt.
At last,
Get it from any View object. just like you did it.
holder.itemview.context.
I hope, it might be helpful for you.
That's because the method startActivity() is a member of the Activity class and not RecyclerView, it simply does not exist there.
Incidentally, a better way that you can start an Activity from a RecyclerView click is via a callback.
I have 3 activities:
Setup
Test
Result
After I passed a test and got a result, I need to get back to SetupActivity with the saved result (it's a custom class). To keep its state saved, I don't apply the finish() method on SetupActivity before opening TestActivity but then I have a problem with passing my results to the SetupActivity because I can't make an intent to already opened activity. So what is the best way to solve this problem? I need to keep the instance of SetupActivity saved and at the same time, I need to pass the data to this activity.
You could use SharedPreferences to save the data in one activity and then get it in the other.
If you are using Dependency Injection, you can send your data easily. If you don't use it, try below.
Pass from First Activity
val intent = Intent(this, SecondActivity::class.java)
intent.putExtra("key", "value")
startActivity(intent)
Get in Second Activity
val value = intent.getStringExtra("key")
Suggestion
Always put keys in constant file for more managed way.
companion object {
val KEY = "key"
}
way is using a Singleton class:
object UserModelSingleton { var KEY: String? = null}
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
I am making an application on AIDE for android, and I'm using an intent to send data from an activity to a normal class.I'm using:
int level= (currentLevel*100)/scale;
Intent i = new Intent(context, caller.class);
i.putExtra("level",level);
context.startActivity(i);
in the class that sends the data ("percentage.class").
int p = getIntent().getIntExtra("level");
in the class that receives the data ("caller.class")
which gives me an error: "Unknown method getIntent()".
What can I do to fix this?
Thanks in advance!
It's easy to say in your question what is the problem. The class in which you are calling getIntent does not inherit the class Activity.
Unlike what other people are saying inheriting Activity is unlikely to give you what you're looking for. What I'm suspecting is that you're calling getIntent in a button or something like this. Since it might be wrapped inside a method that isn't directly pointing to your activity. You should "keep" a pointer to the activity.
Usually, what you are looking for should be in the context. Calling context.getIntent might work if your context is the thing I "believe" it should be. Show more to give us a better idea of what is going on. Because since getIntent is calling from the activity. getIntent is the same as writing this.getIntent but Java implicitely calls function on this and then on the global scope (the thing you import).
If you want to avoid this problem, alway call it from this and when you're calling from within a Handler, you can keep references in your class to the current activity. I'm not so sure but on some object, you should have a function getActivity that will return the activity in which they are located.
you could have something like this. obj.getActivity().getIntent()...
Check this out: What does getActivity() mean?
The getIntent() method belongs to the Activity class. You will get this error if your class does not extend Activity. Try extending Activity and see if it works.