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

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();

Related

Kotlin App: IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first

I was developing an App in kotlin, which let the users publish and contract differents services from the App.
So, in the fragment where I manage the publication of a new services, I get the following error when I try to raise a custom AlertDiolog to ask for the details of the service.
IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
The code of my fragment is the next:
package com.example.appadoskotlin2.ui.publish
import android.annotation.SuppressLint
import android.content.ContentValues
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.appadoskotlin2.R
import com.example.appadoskotlin2.data.Service
import com.example.appadoskotlin2.databinding.FragmentPublishBinding
import com.example.appadoskotlin2.ui.adapters.AdapterPublish
import com.example.appadoskotlin2.ui.diologs.PublishDiolog
import com.example.appadoskotlin2.ui.utils.SimpleDividerItemDecoration
import com.google.firebase.database.*
class PublishFragment : Fragment(), (Service) -> Unit, PublishDiolog.ConfirmationDialogListener {
private lateinit var publishViewModel: PublishViewModel
private var _binding: FragmentPublishBinding? = null
private lateinit var rvPublish: RecyclerView
private lateinit var adapter: AdapterPublish
//TODO("Inicialmente proporcionamos los servicios de manera local.
// En el futuro hacerlo a traves de una API.")
private lateinit var user_description: String
private lateinit var linearLayoutManager: LinearLayoutManager
// This property is only valid between onCreateView and
// onDestroyView.
//private val binding get() = _binding!!
private var services = ArrayList<Service>()
private lateinit var database: DatabaseReference
override fun onViewStateRestored(savedInstanceState: Bundle?) {
super.onViewStateRestored(savedInstanceState)
user_description = savedInstanceState?.getString("user_des").toString()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?,
): View? {
publishViewModel =
ViewModelProvider(this).get(PublishViewModel::class.java)
_binding = FragmentPublishBinding.inflate(inflater, container, false)
val root: View = _binding!!.root
//TODO("Cargar array services con servicios de BBDD.")
val postListener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
// Get Post object and use the values to update the UI
if(dataSnapshot != null){
this#PublishFragment.services = ArrayList()
services = dataSnapshot.getValue() as ArrayList<Service>
}else{
Toast.makeText(context, "No hay ningun servicio disponible", Toast.LENGTH_LONG).show()
}
}
override fun onCancelled(databaseError: DatabaseError) {
// Getting Post failed, log a message
Toast.makeText(context, "Operación cancelada", Toast.LENGTH_LONG).show()
Log.w(ContentValues.TAG, "loadPost:onCancelled", databaseError.toException())
}
}
database = FirebaseDatabase.getInstance().reference
database.addValueEventListener(postListener)
services.add(Service("Ofertar", "Carpinteria"))
services.add(Service("Ofertar", "Fontaneria"))
services.add(Service("Ofertar", "Electricidad"))
services.add(Service("Ofertar", "Otros"))
rvPublish = root.findViewById(R.id.rvPublish)
rvPublish.addItemDecoration(SimpleDividerItemDecoration(this.context, R.drawable.line_divider))
linearLayoutManager = LinearLayoutManager(this.context)
rvPublish.layoutManager = linearLayoutManager
adapter = AdapterPublish(this, services)
rvPublish.adapter = adapter
return root
}
override fun invoke(s: Service) {
//TODO("Mostrar Alert Diolog pidiendiendo los detalles del servicio: descripción y precio")
//TODO("Si es "Otros" permitir al usuario que introduzca la descripcion")
//val parent = view?.parent as ViewGroup
//parent.removeAllViewsInLayout()
//parent.removeAllViews()
val parent = this.parentFragment?.requireView() as ViewGroup
parent.removeAllViews()
showdialog(s)
}
fun showdialog(s:Service){
fragmentManager?.let {
val confirmationDialogFragment = PublishDiolog
.newInstance(
"Confirmar: " + s.type,
"Introduzca la descripción y precio."
)
//TODO("java.lang.IllegalStateException: The specified child already has a parent.
// You must call removeView() on the child's parent first.")
if(confirmationDialogFragment.parentFragment != null){
(confirmationDialogFragment.parentFragment as ViewGroup).removeAllViews()
}
confirmationDialogFragment.setTargetFragment(this, 0)
// confirmationDialogFragment.
confirmationDialogFragment.show(it, "Confirm")
}
}
override fun onDialogPositiveClick(dialog: DialogFragment) {
//TODO("add to remomte DDBB la publicación del servicio.")
//TODO("Rescatar los servicios publicados para usarlos en el ContractFragment")
Toast.makeText(context, "Servicio publicado correctamente", Toast.LENGTH_LONG).show()
}
override fun onDialogNegativeClick(dialog: DialogFragment) {
Toast.makeText(context, "Operación cancelada", Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
#SuppressLint("UseRequireInsteadOfGet")
override fun onDestroyView() {
super.onDestroyView()
_binding = null
if (view != null) {
val parent = view!!.parent as ViewGroup
parent.removeAllViews()
}
}
}
And the custom diolog I try to raise is the following:
package com.example.appadoskotlin2.ui.diologs
import android.annotation.SuppressLint
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
import android.text.InputType
import android.view.View
import android.widget.EditText
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import com.example.appadoskotlin2.R
import com.example.appadoskotlin2.R.id.dialog_publish
import com.example.appadoskotlin2.ui.publish.PublishFragment
import com.google.android.material.button.MaterialButton
import com.google.android.material.textfield.TextInputEditText
private const val TITLE = "Confirmar"
private const val DESCRIPTION = "description_param"
private const val PRICE = "20"
class PublishDiolog: DialogFragment() {
internal lateinit var listener: ConfirmationDialogListener
private var title: String? = null
private var description: String? = null
private var price: String? = null
private lateinit var input: TextInputEditText
private lateinit var input_price: TextInputEditText
private lateinit var btn_publish: MaterialButton
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
listener = targetFragment as ConfirmationDialogListener
} catch (e: ClassCastException) {
throw ClassCastException((targetFragment.toString() + " must implement ConfirmationDialogListener"))
}
arguments?.let {
title = it.getString(TITLE)
description = it.getString(DESCRIPTION)
price = it.getString(PRICE)
}
}
//TODO("Crear custom Diolog para solicitar descripción y precio del servicio.")
#SuppressLint("ResourceType")
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return activity?.let {
val builder = AlertDialog.Builder(it)
title?.let { title -> builder.setTitle(title) }
val inflater = requireActivity().layoutInflater
val view: View = inflater.inflate(R.layout.dialog_publish, null, false)
builder.setView(view)
initView(view)
input.setHint("Descripción")
input.inputType = InputType.TYPE_CLASS_TEXT
input_price.setHint("€")
input_price.inputType = InputType.TYPE_CLASS_NUMBER
builder
.setMessage(description)
.setPositiveButton("Yes",
DialogInterface.OnClickListener { _, _ ->
var d_Text = input.text.toString()
savedInstanceState?.putString("user_des", d_Text)
listener.onDialogPositiveClick(this)
})
.setNegativeButton("No",
DialogInterface.OnClickListener { _, _ ->
listener.onDialogNegativeClick(this)
})
.setView(input)
builder.create()
} ?: throw IllegalStateException("Activity cannot be null")
}
private fun initView(view: View) {
input = view.findViewById(R.id.description_input_text)
input_price = view.findViewById(R.id.price_input_edit)
btn_publish = view.findViewById(R.id.btn_publish)
}
interface ConfirmationDialogListener {
fun onDialogPositiveClick(dialog: DialogFragment)
fun onDialogNegativeClick(dialog: DialogFragment)
}
companion object {
#JvmStatic
fun newInstance(title: String?, description: String) =
PublishDiolog().apply {
arguments = Bundle().apply {
putString(TITLE, title)
putString(DESCRIPTION, description)
putString(PRICE, price)
}
}
}
}
As you see I try to called the removeAllVIews() method into parent fragment but doen't work anyway.
The main point is I tryy to remove the child's parent, on differents ways an anyone works..
If you know differents options why the error comes, take thanks in advance !
[EDIT]
Added the completed logcat message of the error:
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first.
at android.view.ViewGroup.addViewInner(ViewGroup.java:5038)
at android.view.ViewGroup.addView(ViewGroup.java:4869)
at android.view.ViewGroup.addView(ViewGroup.java:4841)
at androidx.appcompat.app.AlertController.setupCustomContent(AlertController.java:657)
at androidx.appcompat.app.AlertController.setupView(AlertController.java:475)
at androidx.appcompat.app.AlertController.installContent(AlertController.java:233)
at androidx.appcompat.app.AlertDialog.onCreate(AlertDialog.java:279)
at android.app.Dialog.dispatchOnCreate(Dialog.java:407)
at android.app.Dialog.show(Dialog.java:302)
at androidx.fragment.app.DialogFragment.onStart(DialogFragment.java:687)
at androidx.fragment.app.Fragment.performStart(Fragment.java:3021)
at androidx.fragment.app.FragmentStateManager.start(FragmentStateManager.java:589)
at androidx.fragment.app.FragmentStateManager.moveToExpectedState(FragmentStateManager.java:300)
at androidx.fragment.app.FragmentManager.executeOpsTogether(FragmentManager.java:2189)
at androidx.fragment.app.FragmentManager.removeRedundantOperationsAndExecute(FragmentManager.java:2100)
at androidx.fragment.app.FragmentManager.execPendingActions(FragmentManager.java:2002)
at androidx.fragment.app.FragmentManager$5.run(FragmentManager.java:524)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:201)
at android.app.ActivityThread.main(ActivityThread.java:6861)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:547)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:873)
I see that in your dialog you have two calls to builder.setView, that seems unusual. Which one do you actually want to be the view?
In the second call you pass input which is a subview of view. So that view already has a parent, and throws the exception. If you want input to be the view to display, instantiate it separate from the binding and pass it.

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));
}
}

Unresolved reference in Kotlin Android Studio fragment

I'm trying to use this package https://github.com/PierfrancescoSoffritti/android-youtube-player to play a youtube video. The error I'm getting is Unresolved reference: youTubePlayerView. I've never worked with fragments before so I'm not understanding what I'm doing wrong? After declaring the button in the oncreateview I could set up a listener for it fine, but for some reason after declaring the youtubePlayerView I cant use it and it results in an unresolved reference. What should I do?
here's my code:
package com.srm325.recyclertest.ui.features.receivesong
import android.net.Uri
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 com.aliakberaakash.cutiehacksproject2020.R
import com.google.android.material.button.MaterialButton
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.YouTubePlayer
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.listeners.AbstractYouTubePlayerListener
import com.pierfrancescosoffritti.androidyoutubeplayer.core.player.views.YouTubePlayerView
import kotlinx.android.synthetic.main.searchsong_layout.*
import java.io.BufferedReader
import java.io.InputStreamReader
import java.net.HttpURLConnection
import java.net.URL
import java.util.concurrent.CountDownLatch
private const val BASE_URL =
"https://www.googleapis.com/youtube/v3/search?key=AIzaSyAcqsfnhOFIyIgX1auWR-SzjcTXOoc3MDE&part=snippet,id&order=viewCount&maxResults=1&q="
class receivefragment : Fragment() {
companion object{
const val GET_FROM_GALLERY = 3
}
lateinit var videostring: String
lateinit var selectedImage:Uri
lateinit var storage: FirebaseStorage
val db = Firebase.firestore
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view: View = inflater.inflate(R.layout.receive_layout, container, false)
val youTubePlayerView: YouTubePlayerView = view.findViewById<YouTubePlayerView>(R.id.youtube_player_view)
val uploadBtn: MaterialButton = view.findViewById(R.id.uploadBtn)
return view
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
uploadBtn.setOnClickListener {
lifecycle.addObserver(youTubePlayerView)
youTubePlayerView.addYouTubePlayerListener(object : AbstractYouTubePlayerListener() {
override fun onReady(youTubePlayer: YouTubePlayer) {
var video123: String = "lOqy8cC72wA";
youTubePlayer.loadVideo(video123!!, 0f)
}
})
}
}
}
private fun requestData(urlstring: String): String? {
return try {
val response = arrayOfNulls<String>(1)
val latch = CountDownLatch(1)
Thread {
try {
Log.d("START", "Starting GET")
val url = URL(urlstring)
val connection = url.openConnection() as HttpURLConnection
connection.requestMethod = "GET"
connection.connectTimeout = 5000
connection.readTimeout = 5000
connection.connect()
Log.d("INFO", urlstring)
Log.d("INFO", Integer.toString(connection.responseCode))
Log.d("INFO", connection.responseMessage)
val rd = BufferedReader(InputStreamReader(connection.inputStream))
var content = ""
var line: String
while (rd.readLine().also { line = it } != null) {
content += """
$line
""".trimIndent()
}
response[0] = content
Log.d("SUCCESS", response[0]!!)
latch.countDown()
} catch (ex: Exception) {
Log.d("ERROR", "Error Processing Get Request...")
var i = 0
while (i < ex.stackTrace.size) {
Log.d("ERROR", ex.stackTrace[i].toString())
i++
}
latch.countDown()
}
}.start()
latch.await()
response[0]
} catch (ex: Exception) {
""
}
}
That's because you try to launch it from the fragment, not from the activity. Try to use requireActivity().youTubePlayerView instead.

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

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.

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