is there a way to create fragment from a activty-adapter imagebutton? - android

i need to create a fragment from activty-adapter on imagebutton click and to pass 3 data along
having trouble with fragment or it says
android.app.Application cannot be cast to android.app.Activity
please help someone
code is in the image
code i tried

Simple way to do it (although you should probably communicate through a viewModel, that is beyond scope of this question):
First take in a listener into the adapter
class FakeAdapter(private val items: List<String>, private val startFragmentCallback: () -> Unit) :
RecyclerView.Adapter<FakeViewHolder>() {
Then, in the onBindViewHolder:
override fun onBindViewHolder(holder: FakeViewHolder, position: Int) {
holder.itemView.setOnClickListener { // in your case this might be holder.commenting - but i don't know what 'commenting' is
startFragmentCallback.invoke()
}
}
Then in the Activity have a private function to create the fragment:
private fun startFragmentCallback() {
// code to start fragment goes here
}
And then use that function when creating your adapter in the activity:
val listOfStrings = listOf("1", "2", "3")
val adapter = FakeAdapter(listOfStrings, this::startFragmentCallback)

Related

Kotlin access activity on recyclerView Adapter

I have recyclerView and after click of card I would like to replace fragments in activity. The problem is I have no access to activity. Here is my code in adapter:
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val itemsViewModel = mList[position]
holder.tagImage.setImageResource(itemsViewModel.tagImage)
holder.tagName.text = itemsViewModel.tagName
holder.tagDescription.text = itemsViewModel.tagDescription
holder.itemView.setOnClickListener {
Log.d(InTorry.TAG, itemsViewModel.tagName)
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
The above replace code works in fragment but in adapter there is "activity?" error.
Kind Regards
Jack
There are multiple ways to solve this problem.
Using Context
You can use the context from holder.itemView and cast it into an Activity.
This is probably the simplest way, however this can be problematic since a Context may represent an Activity, a Service, an Application, etc. in which case it may lead to a ClassCastException when used simply.
Using Callback
You can set up a callback from your Adapter to your Activity or Fragment and then replace your Fragment.
Use JetPack Navigation
This is my personal favorite as the latest versions allow you to access NavController from Activity, Fragment or any View in the hierarchy to navigate. This is just one of many benefits of using this library.
Here is a link to Jetpack Navigation.
The Simplest and Safer way to solve this is my using Callback from your holder to activity. Below is the step by step process :
Decalare an Interface
interface OnItemClick {
fun onClick()
}
Implement that interface in you Activity and put the desired code
class MainActivity : OnItemClick {
...
override onClick() {
// Do whatever you want
val fragment = ProductsFragment()
val transaction = fragmentManager.beginTransaction()
transaction.replace(R.id.homeFragmentsContainer, fragment)
transaction.commit()
}
}
Create a variable of that Interface type in you Adapter and in your onBindViewHolder method invoke that interface
class MyAdapter(val listener : OnItemClick) {
...
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
...
listener.onClick()
}
Finally pass that interface to your Adapter from you Activity
class MainActivity : OnItemClick {
val adapter = MyAdapter(this)
...
}
NOTE : Please don't pass activity to context here and there you will get unexpected result and most probably a crash.
After watching this video https://www.youtube.com/watch?v=WqrpcWXBz14
I managed to do it this way
In Adapter
class TagsAdapter(var mList: List<TagsViewModel>) :
RecyclerView.Adapter<TagsAdapter.ViewHolder>() {
var onItemClick: ((TagsViewModel) -> Unit)? = null//click listener STEP 1!!!
override fun onBindViewHolder(holder: TagsAdapter.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
onItemClick?.invoke(itemsViewModel)//click listener STEP 2!!!
}
}
}
And in Fragment
class TagsFragment : Fragment() {
private lateinit var tagsRecyclerView: RecyclerView
private var tagsArray = ArrayList<TagsViewModel>()
private lateinit var adapter: TagsAdapter
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
adapter = TagsAdapter(tagsArray)
tagsRecyclerView.adapter = adapter
adapter.onItemClick = {//click listener STEP 3!!!
val fragment = ProductsFragment()
val transaction = activity?.supportFragmentManager?.beginTransaction()
transaction?.replace(R.id.homeFragmentsContainer, fragment)
//transaction?.disallowAddToBackStack()
transaction?.commit()
}
}
}
It looks very clean and easy. I don't know is a correct way but it works

How to get data from RecyclerView adapter into the fragment after clicking on it?

I am trying to get data from RecylerView adapter into the fragment, after clicking on that data.
I have tried solving this question using interface. But my app is crashing after clicking on it.
Here is the code of the adapter:-
class SearchPlaceAdapter(
private var mContext: Context,
private var mPlaces: List<String>,
private var isFragment: Boolean = false,
): RecyclerView.Adapter<SearchPlaceAdapter.ViewHolder>(){
private val onPlaceClickListener: MainActivity.OnPlaceClickListener? = null
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(mContext).inflate(R.layout.rv_search_place, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val place = mPlaces[position]
holder.place.text = place
holder.place.setOnClickListener {
onPlaceClickListener!!.onPlaceClick(place)
}
}
override fun getItemCount(): Int {
return mPlaces.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var place: TextView =itemView.findViewById(R.id.searchPlaceTV)
}
}
here is MainActivity code:-
interface OnPlaceClickListener {
fun onPlaceClick(place: String?)
}
here is my fragment code to get the data:-
override fun onPlaceClick(place: String?) {
newPlace.text = place
}
I am getting this as an error. The error is in the adapter:-
java.lang.NullPointerException
at com.ehie.recyclerview.adapter.SearchPlaceAdapter.onBindViewHolder$lambda-0(SearchPlaceAdapter.kt:31)
at com.ehi.recyclerview.adapter.SearchPlaceAdapter.$r8$lambda$KIVoR28fNIxsomM1sHTPNEhSuXQ(Unknown Source:0)
at com.ehie.recyclerview.adapter.SearchPlaceAdapter$$ExternalSyntheticLambda0.onClick(Unknown Source:4)
at android.view.View.performClick(View.java:7792)
at android.widget.TextView.performClick(TextView.java:16112)
at android.view.View.performClickInternal(View.java:7769)
at android.view.View.access$3800(View.java:910)
at android.view.View$PerformClick.run(View.java:30218)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loopOnce(Looper.java:226)
at android.os.Looper.loop(Looper.java:313)
at android.app.ActivityThread.main(ActivityThread.java:8751)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:571)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1135)
The problem is that you are not initializing your onPlaceClickListener anywhere in the adapter, that's why it is throwing NullPointerException when you do onPlaceClickListener!!.onPlaceClick(place). You should pass it as an argument to the adapter.
class SearchPlaceAdapter(
private val onPlaceClickListener: OnPlaceClickListener,
private val mPlaces: List<String>,
private val isFragment: Boolean = false,
): RecyclerView.Adapter<SearchPlaceAdapter.ViewHolder>() {
While creating the adapter from the fragment, you can pass this for the onPlaceClickListener parameter (it will work because your fragment implements the OnPlaceClickListener interface).
Also, you don't need to pass mContext to the adapter. In onCreateViewHolder you can use parent.context in place of mContext.
An alternate approach to sending data from adapter to fragment is to use a lambda function instead of an interface.
class SearchPlaceAdapter(
private val mPlaces: List<String>,
private val isFragment: Boolean = false,
private val onPlaceClick: (String?) -> Unit,
): RecyclerView.Adapter<SearchPlaceAdapter.ViewHolder>() {
...
holder.place.setOnClickListener {
onPlaceClick(place)
}
...
}
// And in your fragment
val adapter = SearchPlaceAdapter(mPlaces = <yourList>) { place ->
newPlace.text = place
}
This approach is much more concise than using an interface.
I was going to say you forgot to set your onPlaceClickListener property, which you did, put also you marked it as private, making it impossible to set it.
Then you use !!. to call it. !! means "crash the app if I happen to put a null value in this property". You should (almost) never use !!. In this case, it would make sense to use a null-safe ?. call, which would silently do nothing if the onPlaceClickListener is not set.
But you need to make onPlaceClickListener public (remove the private keyword in front of it) and set its value in the Activity or Fragment. Or alternatively, you could move it into your constructor to require it.
You should mark your interface as a fun interface to make it easier to work with. It will allow you to define it using a lambda (way less boilerplate).
Also, in my opinion, it is preferable not to make your Fragment directly implement the listener. It is cleaner (better encapsulation) to define your listener as a separate class instance, for example:
// In fragment:
mySearchPlaceAdapter.onPlaceClickListener = MainActivity.OnPlaceClickListener { place ->
newPlace.text = place
}

How can I change or replace layout on the right by clicking on a recycleview item on the left same page- android kotlin

I have a layout that is divided into two, the RecyleView on the left and the details on the right. I would like that when I click an item on the left the layout on the right is replace based on the item clicked. Can someone help me achieve this, or help with a better approach to this. Thank you
You can try this simple solution, use interface.
In your adapter create a interface which is used for implement onClick in your item. Then, call it in onBindViewHolder. For example:
class YourAdapter(val onclick: OnClickListener): RecyclerView.Adapter<RecyclerView.ViewHolder>() {
interface OnClickListener {
fun onClickItem(position: Int)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
onclick.onClickItem(position) // Here i will pass position for Ex, you can pass anything you want depend on
// what's your declared in parameter of onClickItem function
}
}
Declare adapter and implement interface in your Activity.
class YourActivity() : YourAdapter.OnClickListener() {
lateinit var adapter: YourAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(binding.root)
adapter = YourAdapter(this)
}
override fun onClickItem(position: Int) {
// now you can set content to the view on the right, i've pass position item of recyclerView over here.
// content will change based on item you click on the left.
// Ex, i have a TextView on the right.
myTextview.settext(position.toString())
}
}
Your answer helped me. But what I did in the onBindViewHolder, I used [position], if the position is say one, then am able to replace the fragment with the new one:
holder.itemView.setOnClickListener {
if (position == 1) {
val activity = it.context as AppCompatActivity
val menFragment = MenFragment()
activity.supportFragmentManager.beginTransaction()
.replace(R.id.frameLayout, menFragment)
.commit()
}
}

Using onClickListener in Recycler View with Retrofit Structure - Kotlin

Hello i have a recyclerview that is filled with some data coming from a Webservice, using Retrofit.
Now I want to implement a onClickListener, so when i click each row of the Recycler View, i can see more data from that object, and tried to work with some examples, but i got stucked
Here is my adapter. I know that in the onCreateViewHolder, i should put in the Return AnunciosViewHolder a second parameter, of the type cellClickListener, but i have no idea what i have to put. I tried this#CellCLickListener and this#cellCLickListener and it gave me error that is is unresolved
class AnuncioAdapter(val anuncios: List<Anuncio>): RecyclerView.Adapter<AnunciosViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnunciosViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.recyclerline, parent, false)
return AnunciosViewHolder(view)
}
override fun getItemCount(): Int {
return anuncios.size
}
override fun onBindViewHolder(holder: AnunciosViewHolder, position: Int) {
return holder.bind(anuncios[position])
}
}
class AnunciosViewHolder(itemView : View, private val cellClickListener: CellClickListener): RecyclerView.ViewHolder(itemView){
private val morada: TextView = itemView.findViewById(R.id.morada)
private val telemovel: TextView = itemView.findViewById(R.id.number)
private val fotografia: ImageView = itemView.findViewById(R.id.image)
fun bind(anuncio: Anuncio) {
morada.text = anuncio.morada
telemovel.text = anuncio.telemovel
itemView.setOnClickListener {
cellClickListener.onCellClickListener(anuncio)
}
I also tried creating an interface
interface CellClickListener {
fun onCellClickListener (data: Anuncio)
}
and in my Activity i put this method and it gives me an error that «overrides nothing»
override fun onCellClickListener(data: Anuncio) {
val intent = Intent(this#ListaAnuncios, DetalhesActivity::class.java)
intent.putExtra(PARAM_ID, data.id.toString())
intent.putExtra(PARAM_MORADA, data.morada)
intent.putExtra(PARAM_TELEMOVEL, data.telemovel)
startActivityForResult(intent, newAnuncioActivityRequestCode1)
Log.e("***ID", data.id.toString())
}
UPDATE
After using the suggestions made by Praveen i was able to clean my Adapter from errors, however i am struggling in the activity part
if it put
val anuncioAdapter = AnuncioAdapter(anuncios, this)
on the beggining of my On Create, it doesn't recognize «anuncios»
However i am declaring my adapter inside the call.enqueue
recyclerView.apply {
setHasFixedSize(true)
layoutManager =
LinearLayoutManager(this#ListaAnuncios)
adapter = AnuncioAdapter(response.body()!!)
}
And it is asking to pass an instance of cellClickListener here, but if i use «this» in here, it is stated that i am trying to pass an instance of the recycler view instead of the CellClickListener
NEW UPDATE
Forgot to put all the call.enqueue method
call.enqueue(object : Callback<List<Anuncio>> {
override fun onResponse(call: Call<List<Anuncio>>, response: Response<List<Anuncio>>) {
if (response.isSuccessful){
recyclerView.apply {
setHasFixedSize(true)
layoutManager =
LinearLayoutManager(this#ListaAnuncios)
adapter = AnuncioAdapter(response.body()!!)
}
}
}
override fun onFailure(call: Call<List<Anuncio>>, t: Throwable) {
Toast.makeText(this#ListaAnuncios, "${t.message}", Toast.LENGTH_LONG).show()
}
}) }
i tried both approaches of #Praveen and #aligur, but still struggling with asking me to put the instance of Clicklistener as the 2nd parameter, but using «this» is putting the instance of the Recycler View and not of the ClickListener
Thank You in advance
and in my Activity i put this method and it gives me an error that
«overrides nothing»
You are not implementing CellClickListener in your activity. Add CellClickListener after your activity's class name declaration
class MainActivity : AppCompatActivity(), CellClickListener {
}
I know that in the onCreateViewHolder, i should put in the Return
AnunciosViewHolder a second parameter, of the type cellClickListener,
but i have no idea what i have to put. I tried this#CellCLickListener
and this#cellCLickListener and it gave me error that is is unresolved
You've to add the private val cellClickListener: CellClickListener parameter to the constructor of AnuncioAdapter, not the ViewHolder. Only then you will be able to pass it from your activity.
Change constructor of AnuncioAdapter to accept a CellClickListener and remove the same from the constructor of AnunciosViewHolder
class AnuncioAdapter(
private val anuncios: List<Anuncio>,
private val cellClickListener: CellClickListener
): RecyclerView.Adapter<AnunciosViewHolder>() {
}
To access this cellClickListener inside AnunciosViewHolder you've to make it an inner class of AnuncioAdapter, which you can make, as it's already tightly coupled with the adapter.
inner class AnunciosViewHolder(itemView : View): RecyclerView.ViewHolder(itemView){
}
Now, on creating an object of AnuncioAdapter inside activity, just pass an instance of cellClickListener using this, as it's already implementing it.
val anuncioAdapter = AnuncioAdapter(anuncios, this)
I think the easiest way is passing function as parameter to RecyclerViewAdapter.
for instance:
RecyclerViewAdapter(val clickListener : () -> Unit)
onCreateViewHolder(){
clickListener.invoke()
}
in your view
adapter = ReceylerViewAdapter({
//do your stuff here
})
Was finally able to find a solution. By Using #Praveen suggestion, and by finding this example https://github.com/velmurugan-murugesan/Android-Example/tree/master/RetrofitWithRecyclerviewKotlin/app/src/main/java/app/com/retrofitwithrecyclerviewkotlin
On the activity i added a new val, before the OnCreate method
lateinit var anuncioAdapter: AnuncioAdapter
Added this on the onCreate (so i could use the first sugestion)
val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
anuncioAdapter = AnuncioAdapter(this,this)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.adapter = anuncioAdapter
And changed the recyclerview.apply {} on the call.enqeue just to
anuncioAdapter.Anuncios(response.body()!!);
And finally created the Anuncios Method on the Adapter
fun Anuncios(anuncio: List<Anuncio>){
this.anuncios = anuncio;
notifyDataSetChanged()
}
With this it works like how i wanted id. Thanks for the help

Saving the instance of Recycler view during orientation change

I have a RecyclerView which was build using an Arraylist. That Arraylist consists of User defined objects named ListItem.
Each recyclerview has a card view. Each CardView holds each ListItem.
I have removed one CardView from that RecyclerView.
When I rotate the screen , A new Activity is created which results in showing the old data. But I want the recyclerview to hold only updated list and should retain the scrolled position.
ListItem class :
class ListItem(var title: String, var info: String, val imageResource: Int) {
}
MainActivity class :
class MainActivity : AppCompatActivity() {
private lateinit var mSportsData: ArrayList<ListItem>
private lateinit var mAdapter: MyAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val gridColumnCount = resources.getInteger(R.integer.grid_column_count)
recycler_view.layoutManager = GridLayoutManager(this,gridColumnCount)
mSportsData = ArrayList()
recycler_view.setHasFixedSize(true)
initializeData()
recycler_view.adapter = mAdapter
var swipeDirs = 0
if (gridColumnCount <= 1) {
swipeDirs = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT
}
val helper = ItemTouchHelper(object : ItemTouchHelper.SimpleCallback(ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT or ItemTouchHelper.UP or ItemTouchHelper.DOWN,swipeDirs) {
override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean {
val from = viewHolder.adapterPosition
val to = target.adapterPosition
Collections.swap(mSportsData,from,to)
mAdapter.notifyItemMoved(from,to)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
mSportsData.removeAt(viewHolder.adapterPosition)
mAdapter.notifyItemRemoved(viewHolder.adapterPosition)
}
})
helper.attachToRecyclerView(recycler_view)
}
private fun initializeData() {
val sportsList : Array<String> = resources.getStringArray(R.array.sports_titles)
Log.d("Printing","$sportsList")
val sportsInfo : Array<String> = resources.getStringArray(R.array.sports_info)
val sportsImageResources : TypedArray = resources.obtainTypedArray(R.array.sports_images)
mSportsData.clear()
for (i in sportsList.indices-1) {
Log.d("Printing","${sportsList[i]},${sportsInfo[i]},${sportsImageResources.getResourceId(i,0)}")
mSportsData.add(ListItem(sportsList[i], sportsInfo[i], sportsImageResources.getResourceId(i, 0)))
}
sportsImageResources.recycle()
mAdapter = MyAdapter(mSportsData,this)
mAdapter.notifyDataSetChanged()
}
fun resetSports(view: View) {
initializeData()
}
}
MyAdapter class :
class MyAdapter(var mSportsData: ArrayList<ListItem>, var context: Context) : RecyclerView.Adapter<MyAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder(LayoutInflater.from(context).inflate(R.layout.wordlist_item,parent,false))
}
override fun getItemCount() = mSportsData.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val listItem = mSportsData.get(position)
holder.bindTo(listItem)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(view: View) {
val currentSport = mSportsData.get(adapterPosition)
val detailIntent = Intent(context, DetailActivity::class.java)
detailIntent.putExtra("title", currentSport.title)
detailIntent.putExtra("image_resource", currentSport.imageResource)
context.startActivity(detailIntent)
}
fun bindTo(currentSport : ListItem){
itemView.heading_textview.setText(currentSport.title)
itemView.description_textview.setText(currentSport.info)
Glide.with(context).load(currentSport.imageResource).into(itemView.image_view)
}
}
}
You can restrict activity restarting in your Manifest if you have same layout for Portrait and Landscape mode.
Add this to your activity in the manifest.
<activity android:name=".activity.YourActivity"
android:label="#string/app_name"
android:configChanges="orientation|screenSize"/>
If you don't want to restrict screen orientation changes, then you can use OnSaveInstanceState method to save your older data when orientation changed. Whatever data you save via this method you will receive it in your OnCreate Method in bundle. Here is the helping link. So here as you have ArrayList of your own class type you also need to use Serializable or Parcelable to put your ArrayList in your Bundle.
Except these making ArrayList as public static is always a solution, But its not a good solution in Object Oriented paratime. It can also give you NullPointerException or loss of data, in case of low memory conditions.
It looks like initializeData is called twice since onCreate is called again on orientation change, you could use some boolean to check if data has been already initialized then skip initializing
What you are doing is you are deleting the values that are passed down to the recyclerview but when the orientation changes the recyclerview reloads from activity and the original data from activity is passed down again and nothing changes, so if you want to save the changes in recyclerview you have to change the original data in the activity so that if the view reloads the data is the same.
I think u initialize adapter in oncreate method in which the whole adapter will be recreated and all datas is also newly created when configuration changes. Because u init data in oncreate method. Try something globally maintain the list and also delete the item in the list in activity when u delete in adapter also. Or try something like view model architecture
Use MVVM pattern in the project. It will manage the orientation state.
MVVM RecyclerView example:
https://medium.com/#Varnit/android-data-binding-with-recycler-views-and-mvvm-a-clean-coding-approach-c5eaf3cf3d72

Categories

Resources