I am using a recyclerView to show my listItems in the home fragment. But I have been stuck on how to open a different activity when items are clicked. How can I do for the next steps? Could you please help me to solve the issue? I would appreciate the help.
HomeFragment.kt
class HomeFragment : Fragment() {
private var _binding: FragmentHomeBinding? = null
private val binding get() = _binding!!
lateinit var recycle1:RecyclerView
private val list = ArrayList<Locations>()
private val adapter:Adapter = Adapter(list)
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
var v =inflater.inflate(R.layout.fragment_home, container, false)
recycle1 = v.findViewById(R.id.rv_item)
recycle1.layoutManager = LinearLayoutManager(activity)
list.clear()
testData()
val adapterr = Adapter(list)
recycle1.adapter = adapterr
adapter.notifyDataSetChanged()
recycle1.setHasFixedSize(true)
return v
}
private fun testData(){
list.add(Locations(R.drawable.book_cafe,"The Book Cafe","20 Martin Rd","Cafe"))
list.add(Locations(R.drawable.citysprouts,"City Sprouts","102 Henderson Road","Cafe"))
list.add(Locations(R.drawable.esplanade,"Library#esplande","8 Raffles Ave","Library"))
list.add(Locations(R.drawable.hf,"Library#Harbourfront","1 HarbourFront Walk","Library"))
list.add(Locations(R.drawable.mangawork,"MangaWork","291 Serangoon Rd","Cafe"))
list.add(Locations(R.drawable.orchard,"Library#orchard","277 Orchard Road","Library"))
list.add(Locations(R.drawable.rabbitandfox,"Rabbit&Fox","160 Orchard Rd","Cafe"))
list.add(Locations(R.drawable.sixlettercoffee,"T6 Letter Coffee","259 Tanjong Katong Rd","Cafe"))
}
}
Adapter.kt
class Adapter (val listItem:ArrayList<Locations>) : RecyclerView.Adapter<Adapter.RecycleViewHolder>(){
inner class RecycleViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val itemImage: ShapeableImageView = itemView.findViewById(R.id.item_image)
val heading: TextView = itemView.findViewById(R.id.item_title)
val detail: TextView = itemView.findViewById(R.id.item_detail)
val category: TextView = itemView.findViewById(R.id.item_categories)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecycleViewHolder {
val view:View = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return RecycleViewHolder(view)
}
override fun getItemCount(): Int {
return listItem.size
}
override fun onBindViewHolder(holder: RecycleViewHolder, position: Int) {
val item = listItem[position]
holder.itemImage.setImageResource(item.itemImage)
holder.heading.text = item.headings
holder.detail.text = item.detail
holder.category.text = item.category
}
Locations. kt
data class Locations(var itemImage:Int,var headings :String,var detail :String,var category :String)
You have to take help of an interface to navigate :
First Create an interface , Suppose name it Navigate
interface Navigate {
fun onRecyclerViewItemClicked(location : Location)
}
Then you have to make use of this interface in the adapter as well as the fragment :
In your adapter , you have to do the following :
class Adapter (val listItem:ArrayList<Locations>) : RecyclerView.Adapter<Adapter.RecycleViewHolder>(){
//Declare listener object
var listener: Navigate? = null
inner class RecycleViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val itemImage: ShapeableImageView = itemView.findViewById(R.id.item_image)
val heading: TextView = itemView.findViewById(R.id.item_title)
val detail: TextView = itemView.findViewById(R.id.item_detail)
val category: TextView = itemView.findViewById(R.id.item_categories)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecycleViewHolder {
val view:View = LayoutInflater.from(parent.context).inflate(R.layout.list_item, parent, false)
return RecycleViewHolder(view)
}
override fun getItemCount(): Int {
return listItem.size
}
override fun onBindViewHolder(holder: RecycleViewHolder, position: Int) {
val item = listItem[position]
holder.itemImage.setImageResource(item.itemImage)
holder.heading.text = item.headings
holder.detail.text = item.detail
holder.category.text = item.category
// Now suppose you want to navigate to another fragment / Activity on click of the itemImage , then do the following
holder.itemImage.setOnClickListener{
listener?.onRecyclerViewItemClicked(item)
}
}
Now in your fragment extend the Navigate Class and implement the override the methods :
class HomeFragment : Fragment() , Navigate {
// Rest of your code
override fun onRecyclerViewItemClicked(location : Location){
// The argument location here is the information of the item that is clicked of
// the RecyclerView
// Here write your code to navigate to the next fragment / activity based on what
//you are using NavController / Fragment Manager
}
}
Set a clickListener in init {} block of your view holder
For eg.
inner class RecycleViewHolder(itemView: View):RecyclerView.ViewHolder(itemView){
val itemImage: ShapeableImageView = itemView.findViewById(R.id.item_image)
val heading: TextView = itemView.findViewById(R.id.item_title)
val detail: TextView = itemView.findViewById(R.id.item_detail)
val category: TextView = itemView.findViewById(R.id.item_categories)
init {
itemView.setOnClickListener { // start your activity using view context }
}
}
Never set your click listener in onBindViewHolder() method as click listener will set multiple times because this method method is called every time items are bound in recycler view.
Reference official doc
Related
I am making recyclerview in kotlin in android studio. I'm trying to set up an event listener that puts a button in the recyclerview and outputs a toast message. Even if this#[activity name] is entered instead of this in context, a toast message is not displayed. What went wrong?
The error code is as follows.
Unresolved reference: #UpdateActivity
class UpdateActivity : AppCompatActivity() {
private val vBinding by lazy {ActivityUpdateBinding.inflate(layoutInflater)}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(vBinding.root)
var recyclerViewAdapter = CustomAdapter()
recyclerViewAdapter.listData = arrayListOf<String>("a", "b", "c")
vBinding.uploadedRecyclerView.adapter = recyclerViewAdapter
vBinding.uploadedRecyclerView.layoutManager = LinearLayoutManager(this)
} // onCreate End
class CustomAdapter:RecyclerView.Adapter<CustomAdapter.Holder>(){
var listData = arrayListOf<String>()
class Holder(val vBinding:UploadedRecyclerBinding):RecyclerView.ViewHolder(vBinding.root){
fun setData(name:String){
vBinding.name.text = name
vBinding.getBtn.setOnClickListener {
**Toast.makeText(this#UpdateActivity, "test", Toast.LENGTH_SHORT).show()**
// ↑A compilation error occurs here.**
}
}
} // Holder end
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
val vBinding = UploadedRecyclerBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return Holder(vBinding)
}
override fun onBindViewHolder(holder: Holder, position: Int) {
val name = listData[position]
holder.setData(name)
}
override fun getItemCount(): Int {
return listData.size
}
} // CustomAdapter end
} // UpdateActivity end
you could try put context into constructor of adapter when you create it, and use this context to show a toast. something like this:
class CustomAdapter(
private val mContext: Context
):RecyclerView.Adapter<CustomAdapter.Holder>(){
and in holder class:
Toast.makeText(mContext, "test", Toast.LENGTH_SHORT).show()
finally, when you create adapter in activity:
var recyclerViewAdapter = CustomAdapter(this)
Normally, we'll create adapter class in another file, so use this#UpdateActivity in adapter is bad idea
Instead of using the local context in Toast, always use applicationContext when showing it. So, you should replace that creation of Toast message as,
Toast.makeText(applicationContext, "test", Toast.LENGTH_SHORT).show()
class day_1 : Fragment() {
// TODO: Rename and change types of parameters
private var param1: String? = null
private var param2: String? = null
private var _binding: FragmentDay1Binding? = null
private val binding get() = _binding!!
private var mediaPlayer: MediaPlayer? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
binding.startbutton.setOnClickListener {
if (mediaPlayer == null) {
mediaPlayer = MediaPlayer.create(activity, R.raw.days)
}
mediaPlayer?.start()
}
binding.pausebutton.setOnClickListener {
if (mediaPlayer?.isPlaying == true) {
mediaPlayer?.pause()
} else {
mediaPlayer?.start()
}
}
binding.stopbutton.setOnClickListener {
mediaPlayer?.stop()
mediaPlayer = null
}
fun onStop() {
super.onStop()
mediaPlayer?.release()
mediaPlayer = null
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentDay1Binding.inflate(inflater,container,false)
return binding.root
}
companion object {
/**
* Use this factory method to create a new instance of
* this fragment using the provided parameters.
*
* #param param1 Parameter 1.
* #param param2 Parameter 2.
* #return A new instance of fragment day_1.
*/
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
day_1().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
what is above is my code. If I click the button, application was shutting down. First, I think that Media Player is problem. but application without setOnClickListener has no problem. Please help me masters.... Is it problem that setOnClickListener is in Fragment?
And Can I amend this code? or I restart project? Perhaps If you know why this problem exist, Please tell me resolution...
You can take a look for fragment lifecycles at here https://developer.android.com/guide/fragments/lifecycle
You are setting on click listener at onCreate method, in fragments views are created at onCreateView method so you are trying to access your view before it is created. So instead of onCreate method you need to set your click listener after your binding/view is ready, for example you can set your click listener before returning binding.root at onCreateView. Also you can set your click listener at onViewCreated method.
Does anyone know of a way to change the value of a textview text, whilst in a fragment. I have tried findviewbyid, however i believe that this does not work whilst in a fragement. Does anyone have an idea on how to identify a textview and change the text value of it. Thanks
class Home : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view = inflater.inflate(R.layout.fragment_home, container, false)
val queue = Volley.newRequestQueue(view.context)
val url = "https://io.adafruit.com/api/v2/alex9301/feeds/test-data/data" //URL
var urlStuff = ""
val stringRequest = StringRequest(Request.Method.GET, url,
{ response ->
// Display the first 500 characters of the response string.
urlStuff = response
val jsonArray = JSONTokener(urlStuff).nextValue() as JSONArray
val id = jsonArray.getJSONObject(0).getString("value") // Get data
Log.i("Val: ", id)
//Updating info on app
val textView = view.findViewById(R.id.temp)
textView.text = "My Text"
},
{ Log.i("b", "That didn't work!") })
queue.add(stringRequest)
return view
}
}
findViewById also works in a fragment, like this:
val view = inflater.inflate(R.layout.fragment_home, container, false)
val textView: TextView = view.findViewById(R.id.temp)
textView.text = "My Text"
I have been reading the different answers here on stackoverflow and on this blog post and tried to implement their solutions but I am still getting the error. The code is from a yt tutorial. I hope someone can help me. Thanks
Error E/RecyclerView: No adapter attached; skipping layout
My Adapter
This is my Main Activity
class MainActivity : AppCompatActivity(), IDrinkLoadListener {
lateinit var drinkLoadListener: IDrinkLoadListener
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
init()
loadDrinkFromFirebase()
}
private fun loadDrinkFromFirebase() {
val drinkModels : MutableList<DrinkModel> = ArrayList()
FirebaseDatabase.getInstance()
.getReference("Drink")
.addListenerForSingleValueEvent(object: ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if(snapshot.exists()) {
for (drinkSnapshot in snapshot.children) {
val drinkModel = drinkSnapshot.getValue(DrinkModel::class.java)
drinkModel!!.key = drinkSnapshot.key
drinkModels.add(drinkModel)
}
drinkLoadListener.onDrinkLoadSuccess(drinkModels)
} else
drinkLoadListener.onDrinkLoadFailed("Drink items not exist")
}
override fun onCancelled(error: DatabaseError) {
drinkLoadListener.onDrinkLoadFailed(error.message)
}
})
}
private fun init() {
drinkLoadListener = this
val gridLayoutManager = GridLayoutManager(this, 2)
recycler_drink.layoutManager = gridLayoutManager
recycler_drink.addItemDecoration(SpaceItemDecoration())
}
override fun onDrinkLoadSuccess(drinkModelList: List<DrinkModel>?) {
val adapter = MyDrinkAdapter(this,drinkModelList!!)
recycler_drink.adapter = adapter
}
override fun onDrinkLoadFailed(message: String?) {
Snackbar.make(mainLayout,message!!,Snackbar.LENGTH_LONG).show()
}
}
class SpaceItemDecoration : RecyclerView.ItemDecoration() {
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
if(parent.getChildLayoutPosition(view) % 2 != 0)
{
outRect.top= 25
outRect.bottom= -25
}else outRect.top = 0
}
}
Thanks in advance!!!
That happens because the adapter is attached to the recycler after an asynchronous operation on onDrinkLoadSuccess
You can attach the adapter to the recycler during onCreate and then the onDrinkLoadSuccess update it with the data.
The ListAdapter class has a submit method for it. If you are using plain recycler is usually something like this:
Recycler ... {
private val dataList = mutableListOf()
fun update(list: List<YourType>) {
dataList.clear()
dataList.addAll(list)
notifyDataDetChanged() //important updates the UI
}
}
There are more specific updates methods like notifyItemRangeChanged, etc.
Also there are helpers for finding difference in the data sets DiffUtil.ItemCallback
I'm trying to get a info box working with Google Maps API. When trying to run the application, and adding a marker with custom info box it crashes.
Here is my code:
class MainMapsActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMap: GoogleMap
private val test: ArrayList<String> = arrayListOf()
private var mLocationPermissionGranted: Boolean = false
private val PERMISSIONS_REQUEST_ACCESS_FINE_LOCATION: Int = 1234
// ^Number isnt definitive, as long as its unique inside the application
private val DEFAULT_ZOOM: Float = 15.0F
private var mLastKnownLocation: Location? = null
private val mDefaultLocation = LatLng(60.312491, 24.484248)
private lateinit var mFusedLocationProviderClient: FusedLocationProviderClient
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main_maps)
// Obtain the SupportMapFragment and get notified when the map is ready to be used.
val mapFragment = supportFragmentManager
.findFragmentById(R.id.map) as SupportMapFragment
mapFragment.getMapAsync(this)
mFusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
}
override fun onMapReady(googleMap: GoogleMap) {
mMap = googleMap
getDeviceLocation()
//PRESSING WILL ADD MARKER
mMap.setOnMapClickListener(GoogleMap.OnMapClickListener { point ->
val builder: AlertDialog.Builder
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
builder = AlertDialog.Builder(this, android.R.style.Theme_Material_Dialog_Alert)
} else {
builder = AlertDialog.Builder(this)
}
val marker = MarkerOptions().position(point)
builder.setTitle("Are you sure you want to add a map location here?")
.setMessage("Are you sure you want to add a map location here?")
.setPositiveButton(android.R.string.yes, DialogInterface.OnClickListener { dialog, which ->
mMap.addMarker(marker)
//CUSTOM MARKER
.setIcon(BitmapDescriptorFactory.fromResource(R.mipmap.pinetree_foreground))
})
.setNegativeButton(android.R.string.no, DialogInterface.OnClickListener { dialog, which ->
// do nothing
})
.show()
true
val mapInfoWindowFragment = supportFragmentManager.findFragmentById(R.id.infoWindowMap) as MapInfoWindowFragment
//Set Custom InfoWindow
val infoWindow = InfoWindow(point, InfoWindow.MarkerSpecification(0, 0), mapInfoWindowFragment)
// Shows the InfoWindow or hides it if it is already opened.
mapInfoWindowFragment.infoWindowManager()?.toggle(infoWindow, true);
})
And here is the error Logcat gives me:
kotlin.TypeCastException: null cannot be cast to non-null type com.example.sampo.luontoalueet.MapInfoWindowFragment
at com.example.sampo.luontoalueet.MainMapsActivity$onMapReady$1.onMapClick(MainMapsActivity.kt:123)
My question is how to change change this statement into non-null type?
val mapInfoWindowFragment = supportFragmentManager.findFragmentById(R.id.infoWindowMap) as MapInfoWindowFragment
Your findfragmentById is returning null (which indicates that your fragment does not exist).
If you're absolutely sure that fragment cannot be null at this point, you need to review your code. Maybe your fragment is not attached to supportFragmentManager?
If you want to handle a case where this fragment might not exist, You can use nullable cast as?:
val mapInfoWindowFragment = supportFragmentManager.findFragmentById(R.id.infoWindowMap) as? MapInfoWindowFragment
Then you can check conditionally if(mapInfoWindowFragment == null) and handle the null case.