Kotlin - How can I use a recyclerView in a fragment? [closed] - android

Closed. This question is not reproducible or was caused by typos. It is not currently accepting answers.
This question was caused by a typo or a problem that can no longer be reproduced. While similar questions may be on-topic here, this one was resolved in a way less likely to help future readers.
Closed 2 years ago.
Improve this question
I started using fragments and I'm still trying to learn how to use them properly. On the project I'm doing I want to use a recyclerView that shows a list of items that are stored in a database. I'm having some trouble doing it though as my recyclerView doesn't show any of the items.
The fragment where I want to show the recyclerView is like this:
package com.example.is4gestaointegrada.fragments
import android.content.Context
import android.net.Uri
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.is4gestaointegrada.Adapter.ArtigoAdapter
import com.example.is4gestaointegrada.DBHandlers.DBHandler
import com.example.is4gestaointegrada.MainActivity.Companion.dbHandler
import com.example.is4gestaointegrada.R
import kotlinx.android.synthetic.main.fragment_ver_artigos.view.*
private const val ARG_PARAM1 = "param1"
private const val ARG_PARAM2 = "param2"
class VerArtigos : Fragment() {
private var param1: String? = null
private var param2: String? = null
private var listener: OnFragmentInteractionListener? = null
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnFragmentInteractionListener) {
listener = context
} else {
throw RuntimeException(context.toString() + " must implement OnFragmentInteractionListener")
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
param1 = it.getString(ARG_PARAM1)
param2 = it.getString(ARG_PARAM2)
}
dbHandler = DBHandler(context!!, null, null, 1)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view : View = inflater.inflate(R.layout.fragment_ver_artigos, container, false)
viewArtigos(view)
return view
}
override fun onDetach() {
super.onDetach()
listener = null
}
interface OnFragmentInteractionListener {
// TODO: Update argument type and name
fun onFragmentInteraction(uri: Uri)
}
private fun viewArtigos(view : View){
val artigosList = dbHandler.getArtigos(context!!)
val adapter = ArtigoAdapter(context!!, artigosList)
val rv : RecyclerView = view.rv_artigos
rv.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
rv.adapter = adapter
}
companion object {
// TODO: Rename and change types and number of parameters
#JvmStatic
fun newInstance(param1: String, param2: String) =
VerArtigos().apply {
arguments = Bundle().apply {
putString(ARG_PARAM1, param1)
putString(ARG_PARAM2, param2)
}
}
}
}
The Handler's function code is this:
fun getArtigos(mCtx : Context) : ArrayList<Artigo>{
val qry = "SELECT * FROM $DB_ARTIGO_TABLE"
val db = this.readableDatabase
val cursor = db.rawQuery(qry, null)
val artigos = ArrayList<Artigo>()
if (cursor.count == 0){
Toast.makeText(mCtx, "Não foram encontrados artigos", Toast.LENGTH_LONG).show()
} else {
while (cursor.moveToNext()){
val artigo = Artigo()
artigo.artigoNome = cursor.getString(cursor.getColumnIndex(DB_ARTIGO_NOME))
artigo.artigoAbreviada = cursor.getString(cursor.getColumnIndex(DB_ARTIGO_ABREVIADA))
artigo.artigoExtensa = cursor.getString(cursor.getColumnIndex(DB_ARTIGO_EXTENSA))
artigo.artigoFamiliaCodigo = cursor.getInt(cursor.getColumnIndex(DB_ARTIGO_FAMILIACODIGO))
artigo.artigoFamiliaDescricao = cursor.getString(cursor.getColumnIndex(DB_ARTIGO_FAMILIADESCRICAO))
artigo.artigoCodigoIVA = cursor.getInt(cursor.getColumnIndex(DB_ARTIGO_CODIGOIVA))
artigo.artigoPreco = cursor.getDouble(cursor.getColumnIndex(DB_ARTIGO_PRECO))
}
}
cursor.close()
db.close()
return artigos
}
The Adapter is like this:
package com.example.is4gestaointegrada.Adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.is4gestaointegrada.DBModels.Artigo
import com.example.is4gestaointegrada.R
import kotlinx.android.synthetic.main.lo_artigos.view.*
class ArtigoAdapter (mCtx : Context, val artigos : ArrayList<Artigo>) : RecyclerView.Adapter<ArtigoAdapter.ViewHolder>(){
val mCtx = mCtx
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){
val txtArtigoNome = itemView.txt_NomeArtigo
val txtArtigoDesc = itemView.txt_ArtigoDesc
val txtArtigoPreco = itemView.txt_ArtigoPreco
}
override fun onCreateViewHolder(p0: ViewGroup, p1: Int): ArtigoAdapter.ViewHolder {
val v = LayoutInflater.from(p0.context).inflate(R.layout.lo_artigos, p0, false)
return ViewHolder(v)
}
override fun getItemCount(): Int {
return artigos.size
}
override fun onBindViewHolder(p0: ArtigoAdapter.ViewHolder, p1: Int) {
val artigo : Artigo = artigos[p1]
p0.txtArtigoNome.text = artigo.artigoNome
p0.txtArtigoDesc.text = artigo.artigoAbreviada
p0.txtArtigoDesc.text = artigo.artigoPreco.toString()
}
}

Your getArtigos function never adds anything to artigos, so you always return an empty ArrayList.
You need to add it to the list in your while (cursor.moveToNext()){ loop.

Related

Recycler View with duplicate rows after refreshing activity

After doing some research i found out that i need to clear the list that i am using each time i refresh the activity, after trying it to refresh the list at different places, the result remains the same and i have decided to give my brain a break and ask for help. The refreshing takes place on the PostDialogFragment class
i have circled the recycler view causing the issue
PostAdapter
package com.example.osrscritic.view
import android.text.method.ScrollingMovementMethod
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.example.osrscritic.databinding.PostRowItemBinding
import com.example.osrscritic.model.Skillvalue
import com.google.firebase.firestore.CollectionReference
import com.google.firebase.firestore.DocumentSnapshot
import com.google.firebase.firestore.QueryDocumentSnapshot
import com.google.firebase.firestore.QuerySnapshot
class PostsAdapter: RecyclerView.Adapter<PostsViewHolder>() {
val postsMutableList : MutableList<DocumentSnapshot> = mutableListOf()
fun setPostsList(statsList: List<DocumentSnapshot>) {
postsMutableList.addAll(statsList)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PostsViewHolder {
val binding = PostRowItemBinding.inflate(LayoutInflater.from(parent.context)
,parent,false)
return PostsViewHolder(binding)
}
override fun onBindViewHolder(holder: PostsViewHolder, position: Int) {
val post = postsMutableList[position]
holder.binding.tvCritic.text = post["critic"].toString()
val posts = post["posts"] as List<String>
var count = 0;
holder.binding.tvActualPost.movementMethod = ScrollingMovementMethod()
for(p in posts) {
holder.binding.tvActualPost.append("${count}: " + p + "\n")
count++
}
}
override fun getItemCount() = postsMutableList.size
}
class PostsViewHolder(val binding: PostRowItemBinding) : RecyclerView.ViewHolder(binding.root)
DisplayUserActivty
package com.example.osrscritic
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.osrscritic.databinding.ActivityDisplayUserBinding
import com.example.osrscritic.view.PostDialogFragment
import com.example.osrscritic.view.PostsAdapter
import com.example.osrscritic.view.StatsAdapter
import com.example.osrscritic.viewmodel.DisplayUserViewModel
import com.google.android.gms.tasks.OnCompleteListener
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.DocumentSnapshot
import org.koin.androidx.viewmodel.ext.android.viewModel
class DisplayUserActivity : AppCompatActivity() {
private val displayUserViewModel : DisplayUserViewModel by viewModel()
lateinit var binding: ActivityDisplayUserBinding
val statsAdapter = StatsAdapter()
val postsAdapter = PostsAdapter()
lateinit var critiquing: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val extras = intent.extras
critiquing = extras?.getString("c")!!
binding = ActivityDisplayUserBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.rsStatsRv.adapter = statsAdapter
binding.rsStatsRv.layoutManager = LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false)
binding.rvPosts.adapter = postsAdapter
binding.rvPosts.layoutManager = LinearLayoutManager(this,
LinearLayoutManager.VERTICAL, false)
configureObservers()
displayUserViewModel.getFirebaseRef()
displayUserViewModel.getOSRSPlayer()
binding.ivWritePost.setOnClickListener {
PostDialogFragment.newInstance(critiquing).show(supportFragmentManager
, PostDialogFragment.KEY)
}
}
private fun configureObservers() {
displayUserViewModel.displayUserLiveData.observe(this, {
statsAdapter.setStatsList(it.skillvalues)
binding.tvRunescapeName.text = String.format("Account Name: %s", it.name)
binding.tvCombat.text = String.format("Combat Level: %s", it.combatlevel.toString())
})
displayUserViewModel.loadingState.observe(this, {
when(it){
true -> binding.pgBar.visibility = View.VISIBLE
false -> binding.pgBar.visibility = View.GONE
}
})
displayUserViewModel.errorData.observe(this, {
Toast.makeText(this, it, Toast.LENGTH_LONG).show()
})
displayUserViewModel.displayUserPostsLiveData.observe(this, {
postsAdapter.postsMutableList.clear()
it.document(critiquing).collection("posts").get()
.addOnSuccessListener {
postsAdapter.setPostsList(it.documents)
}
})
}
}
PostDialogFragment
package com.example.osrscritic.view
import android.app.AlertDialog
import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.util.Log
import androidx.fragment.app.DialogFragment
import android.widget.LinearLayout
import android.widget.TextView
import android.widget.EditText
import android.view.Gravity
import androidx.core.app.ActivityCompat.recreate
import com.example.osrscritic.DisplayUserActivity
import com.example.osrscritic.viewmodel.DisplayUserViewModel
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FieldPath
import com.google.firebase.firestore.FieldValue
import com.google.firebase.firestore.SetOptions
import org.koin.androidx.viewmodel.ext.android.viewModel
class PostDialogFragment: DialogFragment() {
private val displayUserViewModel : DisplayUserViewModel by viewModel()
val currentUser = FirebaseAuth.getInstance().currentUser
lateinit var critiquing: String
val postsAdapter = PostsAdapter()
companion object {
const val KEY: String = "KEY2"
//lateinit var param: String
fun newInstance(text: String): PostDialogFragment {
val args = Bundle()
val postDialogFragment = PostDialogFragment()
args.putString(KEY, text)
postDialogFragment.arguments = args
return postDialogFragment
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
critiquing = arguments?.getString(KEY)!!
Log.d("*******8", critiquing)
val builder = AlertDialog.Builder(activity)
val layout = LinearLayout(activity)
val parms = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
layout.orientation = LinearLayout.VERTICAL
layout.layoutParams = parms
layout.gravity = Gravity.CLIP_VERTICAL
layout.setPadding(2, 2, 2, 2)
val tv = TextView(activity)
tv.text = "Text View title"
tv.setPadding(40, 40, 40, 40)
tv.gravity = Gravity.CENTER
tv.textSize = 20f
val et = EditText(activity)
var etString = ""
et.addTextChangedListener(object: TextWatcher{
override fun beforeTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun onTextChanged(p0: CharSequence?, p1: Int, p2: Int, p3: Int) {
}
override fun afterTextChanged(p0: Editable?) {
Log.d("etText", et.text.toString())
etString = et.text.toString()
}
})
val tv1 = TextView(activity)
tv1.text = "Input Student ID"
val tv1Params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
tv1Params.bottomMargin = 5
//layout.addView(tv1, tv1Params)
layout.addView(
et,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
)
//param = arguments?.getString(KEY)!!
builder.setTitle("New Post")
builder.setView(layout).setPositiveButton("Done", object: DialogInterface.OnClickListener{
override fun onClick(p0: DialogInterface?, p1: Int) {
activity?.let {
displayUserViewModel.displayUserPostsLiveData.observe(it, {
val post : MutableMap<String, Any> = mutableMapOf()
val posts : MutableList<String> = mutableListOf()
posts.add(etString)
post["critic"] = currentUser?.email!!
post["posts"] = posts
if(it.document(critiquing).collection("posts").document().equals(null)) {
it.document(critiquing).collection("posts")
.document(currentUser?.email!!).set(post)
}
it.document(critiquing).collection("posts")
.document(currentUser?.email!!).update("posts", FieldValue.arrayUnion(etString))
})
}
activity!!.finish()
activity!!.overridePendingTransition( 0, 0);
startActivity(activity!!.intent)
//trying to clear the list here once being refreshed
postsAdapter.postsMutableList.clear()
activity!!.overridePendingTransition( 0, 0);
}
})
builder.setNegativeButton("Cancel", object:DialogInterface.OnClickListener{
override fun onClick(p0: DialogInterface?, p1: Int) {
dismiss()
}
})
return builder.create()
}
}
Overriding those usually solves this kind of issues:
getItemId: return long that uniquely identifies every item. For example, you could use an autoincrementing variable id for each new item object, or some hashcode. This should be paired with setHasStableIds(true). (By default it returns RecyclerView.NO_ID which value is -1).
This helps to make sure RecyclerView e.g. identifies items properly, knows when OnBindViewHolder must be called or to return precise values on getAdapterPosition().
getItemCount: return an int with current number of items. Usually the Array.size() where the data is. (By default it returns 0).
getItemType: If there's multiple view types, return a unique number for each type of ViewHolder (usually starting with 0). (By default it returns 0).

intent.putExtra: None of the following functions can be called with the arguments supplied

I'm trying to pass data from one activity to another through an intent with putExtra. The thing is, I get the error that says: None of the following functions can be called with the arguments supplied.
I'm not sure why it's not working, since the variable that I'm referencing is in the viewholder. Any clue as to what's going on and how to fix it would help a lot.
This is my recycler adapter:
package com.example.newsapp
import android.content.Intent
import android.icu.text.CaseMap
import android.net.Uri
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.ImageView
import android.widget.TextView
import androidx.core.content.ContextCompat.startActivity
import androidx.recyclerview.widget.RecyclerView
import com.example.newsapp.databinding.NewsItemBinding
import com.squareup.picasso.Picasso
class RecyclerAdapter (
private var Titles: List<String>,
private var Images: List<String>,
private var Authors: List<String>,
private var Descriptions: List<String>
) : RecyclerView.Adapter<RecyclerAdapter.ViewHolder>(){
inner class ViewHolder(
view: View
): RecyclerView.ViewHolder(view){
private val binding = NewsItemBinding.bind(view)
val itemTitle: TextView = binding.tvTitle
val itemImage: ImageView = binding.ivNewsImage
fun bind(urlToImage:String){
Picasso.get().load(urlToImage).into(binding.ivNewsImage)
}
init {
itemImage.setOnClickListener{
val intent = Intent(view.context, PostActivity::class.java)
intent.putExtra("title",itemTitle)
view.context.startActivity(intent)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val v = LayoutInflater.from(parent.context).inflate(R.layout.news_item, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.itemTitle.text = Titles[position]
val itemImage = Images[position]
holder.bind(itemImage)
}
override fun getItemCount(): Int {
return Titles.size
}
}
The part with the issue is this one:
init {
itemImage.setOnClickListener{
val intent = Intent(view.context, PostActivity::class.java)
intent.putExtra("title",itemTitle) view.context.startActivity(intent)
}
}
This is my main activity:
package com.example.newsapp
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.newsapp.databinding.ActivityMainBinding
import kotlinx.coroutines.*
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import java.lang.Exception
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var adapter: RecyclerAdapter
private val newsTitles = mutableListOf<String>()
private val newsImages = mutableListOf<String>()
private val newsAuthors = mutableListOf<String>()
private val newsDescriptions = mutableListOf<String>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
makeAPIRequest()
}
private fun initRecyclerView() {
adapter = RecyclerAdapter( newsTitles, newsImages)
binding.rvNews.layoutManager = LinearLayoutManager(this)
binding.rvNews.adapter = adapter
}
private fun addToList(title:String, image:String, author:String, description:String){
newsTitles.add(title)
newsImages.add(image)
newsAuthors.add(author)
newsDescriptions.add(description)
}
private fun makeAPIRequest() {
val api = Retrofit.Builder()
.baseUrl("https://newsapi.org/")
.addConverterFactory(GsonConverterFactory.create())
.build()
.create(APIService::class.java)
GlobalScope.launch(Dispatchers.IO){
val response = api.getNews()
val posts = response.body()
try{
if (posts != null) {
for(art in posts.Articles){
Log.i("Main Activity", "Result = $art")
addToList(art.Title,art.urlToImage, art.Author, art.Description)
}
}
withContext(Dispatchers.Main){
initRecyclerView()
}
} catch (e:Exception){
Log.e("Main Activity", e.toString())
}
}
}
}
intent.putExtra("title",itemTitle)
Here, itemTitle is a TextView. You cannot pass a TextView between activities. You could switch to:
intent.putExtra("title",itemTitle.text)
...to put the text from the TextView into the extra.
Also, you might want to consider whether these should be separate activities or just two fragments (or composables) in a single activity.

RecyclerView add dynamically item. NotifyDataChanged doesn't work

In this app, i am adding addresses to account and saving them in Realtime database (firebase).I want also to display them in recyclerview but they aren't visible.
Here visualization of my problem : https://youtu.be/OdlZNUQnA-k
The Code should work like this
Addressfragment Contains AddressesRecyclerview
AddAddressfragment for adding new Address
And it goes back to AddressFragment when new Address has been added.
Also when i tried to display all items from one array like for example postcode .It display only last added item. Even on for each loop. Like last item is deleted after adding
I understand that it need something like notifyDataSetChanged() but it doesnt work here
Here is The code:
AddressFragment:
package com.querto.fragments.address
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.querto.R
import com.querto.adapters.AddressAdapter
import com.querto.viewmodel.MainActivityViewModel
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_address.view.*
class AddressFragment : Fragment() {
private lateinit var mMainActivityViewModel: MainActivityViewModel
private lateinit var database: DatabaseReference
private lateinit var mAuth: FirebaseAuth
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_address, container, false)
database = FirebaseDatabase.getInstance().reference
mAuth = FirebaseAuth.getInstance()
mMainActivityViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(requireActivity().application).create(
MainActivityViewModel::class.java)
if(mAuth.currentUser==null){
Toast.makeText(requireContext(), "To add address please login",Toast.LENGTH_SHORT).show()
activity?.nav_view?.setCheckedItem(R.id.login)
activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.loginFragment)?.commit()
}
view.recyclerViewAddress.layoutManager = LinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false)
view.recyclerViewAddress.adapter = AddressAdapter(requireContext(), mMainActivityViewModel.list_of_addresses)
view.add_address_btn.setOnClickListener {
activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addAddressFragment)?.commit()
}
return view
}
}
Adapter:
package com.querto.adapters
import android.app.Application
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.RecyclerView
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.querto.R
import com.querto.model.Address
import com.querto.viewmodel.MainActivityViewModel
import kotlinx.android.synthetic.main.my_address_row.view.*
class AddressAdapter(contextAdapter: Context, addresses: ArrayList<Address>):
RecyclerView.Adapter<AddressAdapter.MyViewHolder>() {
private var mMainActivityViewModel: MainActivityViewModel
private val context: Context = contextAdapter
private val local_addreses : ArrayList<Address> = addresses
private var database: DatabaseReference
private var mAuth: FirebaseAuth
init {
mMainActivityViewModel = ViewModelProvider.AndroidViewModelFactory.getInstance(context.applicationContext as Application).create(
MainActivityViewModel::class.java)
database = FirebaseDatabase.getInstance().reference
mAuth = FirebaseAuth.getInstance()
}
class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val currentTitle = itemView.address_title
val currentId = itemView.address_id
val currentStreet = itemView.address_street
val currentPostcode = itemView.address_postcode
val currentHouseNumber = itemView.address_number
val currentAddressCityName = itemView.address_city
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(LayoutInflater.from(context).inflate(R.layout.my_address_row, parent, false))
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.currentTitle.text = local_addreses[position].name
holder.currentId.text = (position + 1).toString()
holder.currentStreet.text =local_addreses[position].street
holder.currentPostcode.text =local_addreses[position].postcode
holder.currentHouseNumber.text = local_addreses[position].house_number
holder.currentAddressCityName.text = local_addreses[position].city_name
}
override fun getItemCount(): Int {
return local_addreses.size
}
fun addAddress(address: Address){
mMainActivityViewModel.list_of_addresses.add(address)
notifyDataSetChanged()
}
}
AddAddress:
package com.querto.fragments.address
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.lifecycle.ViewModelProvider
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.FirebaseDatabase
import com.querto.R
import com.querto.adapters.AddressAdapter
import com.querto.model.Address
import com.querto.viewmodel.MainActivityViewModel
import kotlinx.android.synthetic.main.fragment_add_address.view.*
class AddAddressFragment : Fragment() {
private lateinit var database: DatabaseReference
private lateinit var mAuth: FirebaseAuth
private lateinit var mMainActivityViewModel: MainActivityViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
var view = inflater.inflate(R.layout.fragment_add_address, container, false)
mMainActivityViewModel =
ViewModelProvider.AndroidViewModelFactory.getInstance(activity?.application!!)
.create(MainActivityViewModel::class.java)
view.addAddressButton.setOnClickListener {
val addressName = view.addAddressName.text.toString()
val addressStreet = view.addAddressStreet.text.toString()
val addressNumber = view.addAddressHouseNumber.text.toString()
val addressZipCode = view.addAddressCityZipCode.text.toString()
val addressCityName = view.addAddressCityName.text.toString()
if(inputCheck(addressName,addressStreet,addressNumber,addressZipCode, addressCityName)){
mAuth = FirebaseAuth.getInstance()
database = FirebaseDatabase.getInstance().reference
addAddress(addressName, addressStreet, addressNumber, addressZipCode, addressCityName)
}else{
Toast.makeText(requireContext(), "Please enter all fields", Toast.LENGTH_SHORT).show()
}
}
return view
}
private fun addAddress(addressName: String, addressStreet: String, addressNumber: String,addressZipCode: String, addressCityName: String) {
val address = Address(mAuth.currentUser?.uid, addressName, addressStreet,addressZipCode, addressNumber, addressCityName)
database.child("addresses").child(database.push().key.toString()).setValue(address).addOnCompleteListener {
if(it.isSuccessful){
val addressAdapter= AddressAdapter(requireContext(), mMainActivityViewModel.list_of_addresses)
addressAdapter.addAddress(address)
activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addressFragment)?.commit()
Toast.makeText(requireContext(), "Added address", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(requireContext(), "Fail at creating address", Toast.LENGTH_SHORT).show()
}
}
}
private fun inputCheck(addressName: String, addressStreet: String, addressNumber: String,addressZipCode: String, adressCityName: String)=
addressName.isNotEmpty() && addressStreet.isNotEmpty() && addressNumber.isNotEmpty() && addressZipCode.isNotEmpty() && adressCityName.isNotEmpty() && addressZipCode.length==5
}
MainActivityViewModel:
class MainActivityViewModel(application: Application) : AndroidViewModel(application) {
private lateinit var database: DatabaseReference
private lateinit var mAuth: FirebaseAuth
val homeFragment = HomeFragment()
val loginFragment = LoginFragment()
val registerFragment = RegisterFragment()
val detailsFragment = DetailsFragment()
val addressFragment = AddressFragment()
val addAddressFragment = AddAddressFragment()
var pizza_names: Array<String> = application.resources.getStringArray(R.array.pizza_titles)
var pizza_desc: Array<String> = application.resources.getStringArray(R.array.pizza_desc)
val pizza_small_price: IntArray = application.resources.getIntArray(R.array.pizza_small_price)
val pizza_medium_price: IntArray = application.resources.getIntArray(R.array.pizza_medium_price)
val pizza_big_price: IntArray = application.resources.getIntArray(R.array.pizza_big_price)
var pizza_img: Array<Int> = arrayOf(R.drawable.napoletana, R.drawable.margherita, R.drawable.estate, R.drawable.pepperone, R.drawable.pancetta, R.drawable.ortolana, R.drawable.marinara, R.drawable.diavola, R.drawable.messicana, R.drawable.quattro_formaggi, R.drawable.sugoza, R.drawable.semola, R.drawable.capriciossa, R.drawable.vulcano, R.drawable.romana, R.drawable.capodanno, R.drawable.primavera, R.drawable.regina, R.drawable.quattro_stagioni, R.drawable.cilento, R.drawable.tirolese, R.drawable.michele, R.drawable.pollo, R.drawable.havana, R.drawable.siciliana, R.drawable.sandra, R.drawable.bari, R.drawable.gringo, R.drawable.angelo, R.drawable.spinaci)
var focaccia_names: Array<String> = application.resources.getStringArray(R.array.foaccia_titles)
var focaccia_desc: Array<String> = application.resources.getStringArray(R.array.foaccia_desc)
val focaccia_price: IntArray = application.resources.getIntArray(R.array.foaccia_price)
var focaccia_img: Array<Int> = arrayOf(R.drawable.base, R.drawable.nutella)
var calzone_names: Array<String> = application.resources.getStringArray(R.array.calzone_titles)
var calzone_desc: Array<String> = application.resources.getStringArray(R.array.calzone_desc)
val calzone_price_normal: IntArray = application.resources.getIntArray(R.array.calzone_normal_price)
val calzone_price_big: IntArray = application.resources.getIntArray(R.array.calzone_big_price)
var calzone_img: Array<Int> = arrayOf(R.drawable.calzone)
var panuozzo_names: Array<String> = application.resources.getStringArray(R.array.panuozzo_titles)
var panuozzo_desc: Array<String> = application.resources.getStringArray(R.array.panuozzo_desc)
val panuozzo_price_normal: IntArray = application.resources.getIntArray(R.array.panuozzo_normal_price)
val panuozzo_price_big: IntArray = application.resources.getIntArray(R.array.panuozzo_big_price)
var panuozzo_img: Array<Int> = arrayOf(R.drawable.panuozzo)
val sosy_names: Array<String> = application.resources.getStringArray(R.array.sosy_titles)
val sosy_price: IntArray = application.resources.getIntArray(R.array.sosy_price)
val napoje_names: Array<String> = application.resources.getStringArray(R.array.napoje_titles)
val napoje_price: IntArray = application.resources.getIntArray(R.array.napoje_price)
val napoje_first_kind: Array<String> = application.resources.getStringArray(R.array.napoje_kinds_one)
val napoje_second_kind: Array<String> = application.resources.getStringArray(R.array.napoje_kinds_two)
val dodatki_names: Array<String> = application.resources.getStringArray(R.array.dodatki_titles)
val dodatki_small_price: IntArray = application.resources.getIntArray(R.array.dodatki_small_price)
val dodatki_medium_price: IntArray = application.resources.getIntArray(R.array.dodatki_medium_price)
val dodatki_big_price: IntArray = application.resources.getIntArray(R.array.dodatki_big_price)
var list_of_addresses = ArrayList<Address>()
private val mutableLoginStatus = MutableLiveData<Boolean>()
val loginStatus: LiveData<Boolean>
get() = mutableLoginStatus
fun checkLogin(username: String, password: String) {
viewModelScope.launch(Dispatchers.IO) {
database = FirebaseDatabase.getInstance().reference
mAuth = FirebaseAuth.getInstance()
mAuth.signInWithEmailAndPassword(username,password).addOnCompleteListener{
if(it.isSuccessful){
mutableLoginStatus.postValue(true)
}else{
mutableLoginStatus.postValue(false)
}
}
}
}
fun shareApp(context: Context) {
val openURL = Intent(android.content.Intent.ACTION_VIEW)
openURL.data = Uri.parse("https://www.facebook.com/1488596184507308/")
context.startActivity(openURL)
}
fun sendMail(context: Context) {
val sendEmail = Intent(Intent.ACTION_SEND)
val email: Array<String> = arrayOf("kontakt#cilento.pl")
sendEmail.setData(Uri.parse("mailto: kontakt#cilento.pl "))
sendEmail.putExtra(Intent.EXTRA_SUBJECT, "Problem z Usługą")
sendEmail.putExtra(Intent.EXTRA_TEXT, "Pizza którą zamówiłem nie przyszła na czas.\n\n\nMoje Dane Kontaktowe: \n\nImie: \nNazwisko: \nAdres: ")
sendEmail.setType("message/rfc822")
sendEmail.putExtra(Intent.EXTRA_EMAIL, email)
val chooser = Intent.createChooser(sendEmail, "Send mail using")
context.startActivity(chooser)
}
}
Address Class:
package com.querto.model
data class Address(
val userId: String?,
val name: String?,
val street: String?,
val postcode: String?,
val house_number: String?,
val city_name: String?
)
After Checking The MainActivityViewModel ,I See that you are not fetching data from the firebase database.
You should add this to AddressFragment class
also Add Variable called addressAdapter in the top of the class
fun getAddresses(){
val ref = FirebaseDatabase.getInstance().reference.child("addresses")
ref.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
val address = dataSnapshot.getValue<Address>()
mMainActivityViewModel.addresses_list.add(address)
addressAdapter.notifyDataChanged()
}
}
override fun onCancelled(databaseError: DatabaseError) {
}
})
}
Also Implementing Address adapter here does not do a thing . just remove it.
private fun addAddress(addressName: String, addressStreet: String, addressNumber: String,addressZipCode: String, addressCityName: String) {
val address = Address(mAuth.currentUser?.uid, addressName, addressStreet,addressZipCode, addressNumber, addressCityName)
database.child("addresses").child(database.push().key.toString()).setValue(address).addOnCompleteListener {
if(it.isSuccessful){
val addressAdapter= AddressAdapter(requireContext(), mMainActivityViewModel.address_title, mMainActivityViewModel.address_street,mMainActivityViewModel.address_post_code, mMainActivityViewModel.address_house_number, mMainActivityViewModel.address_city_name)
addressAdapter.addAddress(addressName, addressStreet,addressZipCode, addressNumber, addressCityName)
activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addressFragment)?.commit()
Toast.makeText(requireContext(), "Added address", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(requireContext(), "Fail at creating address", Toast.LENGTH_SHORT).show()
}
}
I will try to explain it here.
When you create a custom RecyclerView adapter usually you suppose to pass data you want to be displayed in the list. For me it is better to pass array Of Class You Made To Pass Data For Single Recylcer View itemArrayList<ClassOfSingleItem>, but it is not nessesary of course.
So when you call notifyDataSetCahnged you notify your adapter that data in this ArrayList was changed. But this call will not work if you create new Adapter every time you add or remove something from the RecyclerView list.
EDIT:
Let me explain what i see: you have button with click listener, when you click it you replace current fragmen with the one where you add data.
view.add_address_btn.setOnClickListener {
activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addAddressFragment)?.commit()
}
then, when you complete editing all fields what you do? replace your addAdressFragment with addressFragment:
val addressAdapter= AddressAdapter(requireContext(), mMainActivityViewModel.list_of_addresses)
addressAdapter.addAddress(address)
activity?.supportFragmentManager?.beginTransaction()?.setCustomAnimations(R.anim.fragment_slide_in_anim, R.anim.fragment_fade_out_anim, R.anim.fragment_slide_out_anim, R.anim.fragment_fade_in_anim)?.replace(R.id.fragment_container, mMainActivityViewModel.addressFragment)?.commit()
inside this method you CREATE new AdressAdapter to call method addAdrees which has notifyDataSetChanged call inside. But recyclerView in your addressFragment has no idea about this new AddressAdapter. This is the main problem. As i mentioned in comment below my answer there are only two ways to update RecyclerView so implement one of them.
Below I show some java code (you mentioned you know java) which can give you an idea:
public class PointActivity extends BaseActivity
implements SearchView.OnQueryTextListener{
private RecyclerView mRecyclerView;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_point);
mRecyclerView = findViewById(R.id.point_list);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
List<PointItem> pointItems = getFilteredAndSortedList();
mRecyclerView.setAdapter(new PointAdapter(this, pointItems));
}
//here we need to update recyclerView
#Override
public boolean onQueryTextSubmit(String query) {
searchResult(query);
return false;
}
public void searchResult(String query) {
if (query.isEmpty()) {
//just full list if query is empty
mRecyclerView.setAdapter(new PointAdapter(this, getFilteredAndSortedList()));
} else {
PointSearchManager pointSearchManager = new PointSearchManager();
List<PointItem> list;
list = Arrays.asList(pointSearchManager.toFilters(getFilteredAndSortedList().toArray(new PointItem[0]), query));
//list filtered with query result
mRecyclerView.setAdapter(new PointAdapter(this, list));
}
}

Can't pass data to another fragment in Android (Kotlin)

I've got a problem. I'm making an application for Android of the client of MQTT and I need to use the same parameters for MqttAndroidClient() method in different fragments. I've already tried to pass them in the bundle, with intent putExtra(), making objects of the class. The bundle and putExtra send the data to another fragment, it shows in the debug mode, but in the target fragment I get nulls.
When I'm trying to receive the value instantiating the first fragment, it sends me the lateinit variable without any value in it. I have no ideas what can I do more. I thought about using setters and getters to get it, but I'm not sure that is the solution.
First fragment where I send the data:
ConnectFragment.kt
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.dzichkovskii.mqttsrm.R
import kotlinx.android.synthetic.main.fragment_connect.*
import org.eclipse.paho.android.service.MqttAndroidClient
import org.eclipse.paho.client.mqttv3.*
import java.io.Serializable
class ConnectFragment : Fragment(){
companion object{
const val SUCCESS_TEXT = "Connection established successfully"
const val FAILURE_TEXT = "Connection wasn't established. Error happened."
const val BLANK_TEXT = "Your inputs cannot be empty. Please, write the correct address or ID."
const val CONNECTION_FAILURE = "Something went wrong. Probably you have no internet. Try later"
const val SENDING_NAME_ADDRESS = "mqttAndroidClientAddress"
const val SENDING_NAME_ID = "mqttAndroidClientId"
const val TAG = "ConnectFragment"
}
lateinit var mqttAndroidClient: MqttAndroidClient
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_connect, container, false)
//I will leave this values just in case I would need to test the connection
//val testClientId = MqttClient.generateClientId()
//val testAddress = "tcp://broker.hivemq.com:1883"
root.findViewById<Button>(R.id.btn_connect).setOnClickListener {
mqttAndroidClient = connect(context, view)
}
return root
}
private fun connect(context: Context?,
view: View?): MqttAndroidClient {
// val inputAddress = view?.findViewById(R.id.tv_broker_address_input) as EditText
// val inputId = view?.findViewById(R.id.tv_client_id_input) as EditText
// val inputPort = view.findViewById(R.id.tv_broker_port_input) as EditText
//
//Making the string the user needs to put more friendly
// val addressStringSimplification = "tcp://" + inputAddress.text.toString() +
// ":" + inputPort.text.toString()
val addressStringSimplification = "tcp://broker.hivemq.com:1883"
val testClientId = MqttClient.generateClientId()
mqttAndroidClient = MqttAndroidClient(context?.applicationContext, addressStringSimplification, testClientId/*inputId.text.toString()*/)
val intent = Intent(this.context, SubscribeFragment::class.java)
val bundle = Bundle()
bundle.putString("testBundle", addressStringSimplification) //bundle here
val subscribeFragment = SubscribeFragment()
subscribeFragment.arguments = bundle
intent.putExtra(SENDING_NAME_ADDRESS, addressStringSimplification) //Intents here
intent.putExtra(SENDING_NAME_ID, testClientId)
// if (inputAddress.isBlank() || inputId.isBlank()
// || inputPort.isBlank() || addressStringSimplification == "tcp://:"){
// displayErrorMessage(BLANK_TEXT, view, this)
// return
// }
// else {
try {
val token = mqttAndroidClient.connect()
token.actionCallback = object : IMqttActionListener {
override fun onSuccess(asyncActionToken: IMqttToken?) {
Log.d(TAG, "Connection is successful")
Toast.makeText(context, SUCCESS_TEXT, Toast.LENGTH_SHORT).show()
hideKeyboard()
return
}
override fun onFailure(asyncActionToken: IMqttToken?, exception: Throwable?) {
Log.d(TAG, "Connection didn't established")
Toast.makeText(context, FAILURE_TEXT, Toast.LENGTH_SHORT).show()
displayErrorMessage(FAILURE_TEXT, view, this#ConnectFragment)
return
}
}
} catch (e: MqttException) {
Log.d(TAG, "Exception caught")
displayErrorMessage(CONNECTION_FAILURE, view, this)
}
return mqttAndroidClient
}
// tv_error.visibility = View.INVISIBLE
}
/**
* This extension function makes strings look less ugly.
*/
private fun EditText.isBlank() = this.text.toString().isBlank()
//}
fun displayErrorMessage(errorString: String, view: View?, fragment: Fragment){
val errorTextView = view?.rootView?.findViewById(R.id.tv_error) as TextView
errorTextView.text = errorString
errorTextView.visibility = View.VISIBLE
fragment.hideKeyboard()
}
fun Fragment.hideKeyboard() {
view?.let { activity?.hideKeyboard(it) }
}
fun Context.hideKeyboard(view: View) {
val inputMethodManager = getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager
inputMethodManager.hideSoftInputFromWindow(view.windowToken, 0)
}
And here is another fragment where I receive data:
SubscribeFragment.kt
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import android.widget.Toast
import androidx.fragment.app.Fragment
import com.dzichkovskii.mqttsrm.R
import com.google.android.material.chip.Chip
import kotlinx.android.synthetic.main.fragment_subscribe.view.*
import org.eclipse.paho.android.service.MqttAndroidClient
import org.eclipse.paho.client.mqttv3.IMqttActionListener
import org.eclipse.paho.client.mqttv3.IMqttToken
import org.eclipse.paho.client.mqttv3.MqttException
class SubscribeFragment : Fragment() {
companion object {
const val TAG = "SubscribeFragment"
const val BLANK_TEXT = "Your inputs cannot be empty. Please, write the correct address or ID."
const val ON_SUCCESS = "You subscribed successfully."
const val ON_FAILURE = "You didn't subscribed to the topic. Probably this topic doesn't exist."
const val CONNECTION_ERROR = "The topic don't exist or you have connection problems. " +
"Check your internet connection or change the topic's name"
const val GETTING_NAME_ADDRESS = "mqttAndroidClientAddress"
const val GETTING_NAME_ID = "mqttAndroidClientId"
}
private var checkedOption: Int = 0 //Default value of qos
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = inflater.inflate(R.layout.fragment_subscribe, container, false)
return root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.chip_group?.setOnCheckedChangeListener { _, checkedId: Int ->
val chip: Chip? = view.findViewById(checkedId)
val qos = chip?.text.toString().toInt()
checkedOption = qos
Log.d(TAG, "Checked option passed with value $checkedOption")
}
view.findViewById<Button>(R.id.btn_subscribe).setOnClickListener {
subscribe()
}
}
private fun subscribe(){
val connectFragment = ConnectFragment()
val mqttAndroidClient = connectFragment.mqttAndroidClient //Instantiation of the class here
val address = this#SubscribeFragment.arguments?.getString("testBundle") // Bundle here
val id = activity?.intent?.getStringExtra(GETTING_NAME_ID) // Intent string here
//val mqttAndroidClient = MqttAndroidClient(context, address, id)
val inputTopic = view?.findViewById(R.id.et_topic) as EditText
val topic = inputTopic.text.toString()
if (inputTopic.isBlank()){
displayErrorMessage(BLANK_TEXT, view, this)
}
try {
Log.d(TAG, "Checked option in subscribe method is $checkedOption")
mqttAndroidClient.subscribe(topic, checkedOption, null, object : IMqttActionListener {
override fun onSuccess(asyncActionToken: IMqttToken) {
Toast.makeText(context, ON_SUCCESS, Toast.LENGTH_SHORT).show()
Log.d(TAG, "Connected successfully")
}
override fun onFailure(
asyncActionToken: IMqttToken,
exception: Throwable
) {
Toast.makeText(context, ON_FAILURE, Toast.LENGTH_SHORT).show()
Log.d(TAG, "Didn't connected")
}
})
} catch (e: MqttException) {
displayErrorMessage(CONNECTION_ERROR, view, this)
}
}
private fun EditText.isBlank() = this.text.toString().isBlank()
}
Thanks for answering!
First of all to create a fragment you should have a newInstance method and this method should except the parameters that you want to pass to the fragment, in your case SubscriberFragment should have following function in companion object
class SubscribeFragment: Fragment() {
companion object {
const val SENDING_NAME_ADDRESS = "mqttAndroidClientAddress"
const val SENDING_NAME_ID = "mqttAndroidClientId"
// Use this function to create instance of your fragment
fun newInstance(addressStringSimplification: String,
testClientId: String): MyFragment {
val args = Bundle()
args.putString(SENDING_NAME_ADDRESS , addressStringSimplification)
args.putString(SENDING_NAME_ID , testClientId)
val fragment = SubscribeFragment()
fragment.arguments = args
return fragment
}
}
}
After this when your fragment is loaded the arguments are delivered to onCreate method and you can extract them as following.
#Override
public void onCreate(Bundle savedInstanceState) {
var sendingName = getArguments().getInt(SENDING_NAME_ADDRESS);
var sendingId = getArguments().getString(SENDING_NAME_ID);
}
Now to the most important part, you seem to think that doing SubscriberFragment() is enough to load the fragment which is not the case. when you want to start SubscriberFragment, you should do the following
// create instance of SubscriberFragment with newInstance function and pass the argguments you want
var someFragment = SubscribeFragment.newInstance(addressStringSimplification,testClientId);
var transaction = getActivity().getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, someFragment); // give your fragment container id in first parameter
transaction.addToBackStack(null);
transaction.commit();

onClick not working in MVVM with DataBinding

For some reason, onClick isn't being registered with my adapter. I'm using the MVVM pattern and I've made sure that all the pieces are tied together but for the life of me I can't figure out why this won't work.
StoreFragment
package com.example.brandroidtest.main
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.navigation.fragment.findNavController
import com.example.brandroidtest.databinding.FragmentStoreBinding
class StoreFragment : Fragment() {
//Will Create a ViewModelProivders object of class DetailViewModel the first time viewModel is used
//Allows us to move this code from on create to the declaration
private val viewModel: StoreViewModel by lazy {
val factory = StoreViewModelFactory(requireNotNull(activity).application)
ViewModelProviders.of(this, factory).get(StoreViewModel::class.java)
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
Log.i("onCreateView", "StoreFragment created")
val binding = FragmentStoreBinding.inflate(inflater)
binding.setLifecycleOwner(this)
binding.viewModel = viewModel
binding.storeList.adapter = StoreAdapter(StoreAdapter.OnClickListener {
viewModel.displayStoreDetails(it)
Log.i("inside OnClickListener", "after displayDetails")
})
Log.i("between adapter.onclick", "and viewModel observe")
viewModel.selectedStore.observe(this, Observer {
Log.i("observe", "inside the selectedStore observe method")
if (null != it) {
this.findNavController().navigate(
StoreFragmentDirections.actionMainListFragmentToDetailFragment(
it
)
)
viewModel.displayStoreDetailsComplete()
}
})
return binding.root
}
}
StoreViewModel
package com.example.brandroidtest.main
import android.app.Application
import android.content.Context
import android.net.ConnectivityManager
import android.util.Log
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.brandroidtest.model.Store
import com.example.brandroidtest.network.StoreAPI
import kotlinx.coroutines.*
enum class StoreAPIStatus {LOADING, DONE, NO_CONNECTION}
class StoreViewModel(application: Application) : AndroidViewModel(application) {
// Response from server: Either Store Data or Failure Message
private val _status = MutableLiveData<StoreAPIStatus>()
// for status of get request
//displayed when there is no internet connection or if the connection is unstable and the data is being loaded
val status: LiveData<StoreAPIStatus>
get() = _status
//internal variable accessed within this file
private val listOfStores = MutableLiveData<List<Store>>()
//external variable for anywhere else
val stores: LiveData<List<Store>>
get() = listOfStores
private val _selectedStore = MutableLiveData<Store>()
val selectedStore: LiveData<Store>
get() = _selectedStore
private var viewModelJob = Job()
private val coroutineScope = CoroutineScope(viewModelJob + Dispatchers.Main)
/**
* Call getStoreData() in init so we can display the result immediately.
*/
init {
Log.i("viewModel init", "inside StoreViewModel init block")
if (isNetworkConnected(application.applicationContext))
getStoreData()
else
// Log.i("Bypassed network call", "")
listOfStores.value = emptyList()
_status.value = StoreAPIStatus.NO_CONNECTION
}
/**
* Sets the value of the status LiveData to the Store API data.
*/
private fun getStoreData() {
Log.i("getStoreData()", " inside getStoreData")
coroutineScope.launch {
try {
Log.i("getStoreData()", "Inside the coroutine before getData")
_status.value = StoreAPIStatus.LOADING
var storeData = async { StoreAPI.retrofitService.getData().stores }.await()
Log.i("getStoreData()", "Inside the coroutine after getData")
_status.value = StoreAPIStatus.DONE
listOfStores.value = storeData
} catch (e: Exception) {
_status.value = StoreAPIStatus.NO_CONNECTION
listOfStores.value = ArrayList()
e.printStackTrace()
}
}
}
override fun onCleared() {
super.onCleared()
viewModelJob.cancel()
}
private fun isNetworkConnected(context: Context): Boolean {
val cm =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager?
return cm!!.activeNetworkInfo != null && cm.activeNetworkInfo.isConnected
}
//will be called to set the store as the one that was clicked
fun displayStoreDetails(store : Store){
Log.i("displayStoreDetails", "inside this method")
_selectedStore.value = store
}
//sets the selected store's value to null so that live data can be updated when we select a new store and not show us the detail apge of the same store
fun displayStoreDetailsComplete() {
Log.i("displayStoreDetailsComplete", "inside this method")
_selectedStore.value = null
}
}
StoreAdapter
package com.example.brandroidtest.main
import android.util.Log
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.example.brandroidtest.model.Store
import com.example.brandroidtest.databinding.ListItemBinding
class StoreAdapter(val onClickListener: OnClickListener) :
ListAdapter<Store, StoreAdapter.StoreViewHolder>(DiffCallback) {
class StoreViewHolder(private var binding: ListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(store: Store) {
binding.store = store
Log.i("Adapter bind", store.storeLogoURL)
binding.executePendingBindings()
}
}
companion object DiffCallback : DiffUtil.ItemCallback<Store>() {
override fun areItemsTheSame(oldItem: Store, newItem: Store): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Store, newItem: Store): Boolean {
return oldItem.storeID == newItem.storeID
}
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): StoreViewHolder {
return StoreViewHolder(ListItemBinding.inflate(LayoutInflater.from(parent.context)))
}
override fun onBindViewHolder(holder: StoreViewHolder, position: Int) {
val store = getItem(position)
Log.i("inside onBindViewHolder", "")
holder.itemView.setOnClickListener {
Log.i("inside onBindViewHolder", "setOnClickListener")
onClickListener.onClick(store)
}
holder.bind(store)
}
class OnClickListener(val clickListener: (store: Store) -> Unit) {
fun onClick(store: Store) {
Log.i("inside onClick", "click is being registered ${store.city}")
return clickListener(store)
}
}
}
StoreDetailFragment
package com.example.brandroidtest.detailed
import android.os.Bundle
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelProviders
import com.example.brandroidtest.R
import com.example.brandroidtest.databinding.FragmentStoreDetailBinding
/**
* A simple [Fragment] subclass.
*/
class StoreDetailFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val application = requireNotNull(activity).application
val binding = FragmentStoreDetailBinding.inflate(inflater)
binding.setLifecycleOwner(this)
val store = StoreDetailFragmentArgs.fromBundle(arguments!!).selectedStore
val viewModelFactory = StoreDetailViewModelFactory(store, application)
binding.viewModel = ViewModelProviders.of(this, viewModelFactory).get(StoreDetailViewModel::class.java)
return binding.root
}
}
StoreDetailViewModel
package com.example.brandroidtest.detailed
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.example.brandroidtest.model.Store
class StoreDetailViewModel(store: Store, application: Application) : AndroidViewModel(application) {
private val _selectedStore = MutableLiveData<Store>()
val selectedStore : LiveData<Store>
get() = _selectedStore
init {
_selectedStore.value = store
}
}
I have no idea why onClick won't work and the Detail Fragment won't show because of it
Here is the project link: https://drive.google.com/open?id=1m8R8HvCt4m0KIp_IwdeO1YdB5yY8A8LK
The issue come from your adapter item layout.
The height of every item show be wrap_content. But you are using a ScrollView as the root view of your item view.
Remove the useless ScrollView and also the next LinearLayout. You layout should become like this:
<LinearLayout
...
android:padding="16dp"/>
<ImageView
android:id="#+id/store_logo"
.../>
<LinearLayout
android:id="#+id/store_detail"
...>
</LinearLayout>

Categories

Resources