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.
Related
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.
I have a button in an activity that when pressed adds 1 to a text view in another activity. However, when I try to access that variable in a different activity, I cannot. How could I solve this without passing intents (making the variable open for use in the whole program)?
You can use companion object in the Activity where you set the desired variable:
class ShowCaseActivity : Activity {
companion object {
var yourVariable = 0
}
}
Now you can use this variable in every file in the project, you just have to import it accordingly like this:
import ShowCaseActivity.Companion.yourVariable
....
val number = ShowCaseActivity.yourVariable
It should be said that this is not a recommended way of solving this issue. You should pass data between Activities with intents.
There is a wall between Activities and you can only pass data over that wall with Intents. For this reason, it is recommended to use only one Activity. You can use Fragments to represent your different screens. Use an Activity-scoped ViewModel to hold properties and LiveData you need to access from both screens.
In your case, you would have a function in the ViewModel that the button calls. The function would increment a private variable and set its value to a public LiveData. The TextView would observe that LiveData so it is automatically updated when the value is changed.
I have a common method to get image from gallery in activity which is accessed by different fragments what is the best way to know in activity method which fragment is calling this method so that I can put appropriate conditions.
I have tried by passing a string in fragment but is there any other clean way
Blockquote I have a common method to get image from gallery in activity which is accessed by different fragments what is the best way to know in activity method which fragment is calling this method so that I can put appropriate conditions.
Could you provide more details on what you are trying to achieve?
If I got it right, you have a method in your Activity that is accessed by different fragments. Is it right? If yes, then there is a high chance that it is not correct. Let me elaborate on this: by letting fragments invoke a method inside the hosting activity you are coupling them to the activity. What if you need to attach the same Fragment to different activities? Anyway, this is not always the case. For example, if you already know that these fragments will always be used with this particular activity, then it is OK.
In general, the communication between activities and fragments is achieved by means of interfaces to allow components to remain decoupled. I higly reccomend you to read the official docs on the topic here.
Anyway, if you want to have such function, and you are using Kotlin, you could exploit reified parameters. For example, in your activity you could have:
inline fun <reified T> invokerName() = T::class.simpleName // Do something with the class
that you can invoke this way:
activity.invokerName<Foo>()
you can pass the Fragment class like this :
(requireActivity() as YourActivity).method(this::class)
and in your activity take all available information about this class :
fun method(comesFromClass: KClass<out Fragment>) {
val simpleName = comesFromClass.simpleName
...
}
I'm new to android, I wanted to know if it is okay to access properties initialized in activity / call activity functions from fragment like this or is it bad practice and I should avoid it.
(requireContext() as BaseActivity).viewModel
(requireContext() as BaseActivity).countryList
(requireContext() as BaseActivity).getSomething()
Your instincts can be right. Breaking changes can be caused by name conflicts, variable shadowing, wrong imports, wrong assignment to values. But these days, the demand for features is increasing, in such that you need the public accessor. Just have this rules in your conscience:
Interfaces are powerful at class to class communication
Inherit what is important, override what is implemented already, pass to param to lessen global var damage
If a variable can be stored in another global form, consider it with regard to size(ram matters), speed of access(ux matters), security(keys matter) and volatility(nulls matter).
Now looking at your code, I can see you have a fragment system that is based on values/functions stored in the main activity, that provides the context for the fragment. If you apply the first point: Your fragment will implement a BaseFragment that already some context cast i.e. lateinit var mainActivity: MainActivity then you can mainActivity.viewModel anywhere in your fragment without casting. And this is cleaner
Applying the second point: in the BaseFragment (that will be inherited by AnotherFragment)
abstract var viewModel: ViewModel
abstract fun initList()
open var countryList = mutableListOf()
open fun onScale(detector: ScaleGestureDetector) { //pinch: increase visible country list like some nice zoom effect .. etc }
if most or all of your fragments need similar functions or variables, make abstract to something you can forget will crash the app, make open for those 'features but I dont need to rewrite so I'll call super.function' functions. Make a var open if some super function overrides it, and just put var if you seriously dont know when you want it and when to change it.
On the third point, Android in the early stages, we learnt the hard way that context doesn't last forever even if your app is running. Rotation and lifecycle functions will swap it rough and fast. So consider other storage ways. I still dont trust requireContext/Activity/view for context, so cast with caution.
A big NO. It is a bad practice to use hardcode references to activity from fragments.
I see that you are using viewmodel, which indicates that you are using MVVM, you should use Sharing data between Activities and Fragments in MVVM made simple with SharedViewModel concept for communicating to viewmodel of the activity.
For communicating to the Activity which hosts your Fragment you should use an interface pattern of communication. from fragment to the activity
Let's say one of your activity won't extend the base activity as it has to extend one activity from a library lets say YouTubeBaseActivity, and it will host a particular fragment now the cast to BaseActivity will never succeed in your activity.
New to Android development here. I am creating a RecyclerView that presents a new activity when certain items are clicked. I searched online and all of them would define a click listener interface, pass the listener to view holders, and ultimately call startActivity from the main activity.
Alternatively, I came up with the following. Since nobody is using this approach, what's wrong with it? It is just a few lines of code in the adapter class and seems to work equally well
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is certainViewHolder -> {
holder.itemView.setOnClickListener {
val intent = Intent(holder.itemView.context, DetailedActivity::class.java)
holder.itemView.context.startActivity(intent)
}
}
else -> ...
}
}
Nothing wrong with your approach and many people use it. Usually first approach used most of time because calling method using interface is good practice. When we create a project using certain architecture like MVP or MVVM or any other we need to call all method using interface. Currently in your project you just need to start a activity but many times we to perform longer and difficult task so it better to perform in relate activity. Through we can collect all method in same place.