Currently I have a recyclerview adapter that gets data from the database and displays it in the dashboard fragment. Once the item is clicked, I want to pass the item id to the details fragment to get the correct items information on the detailed view. How can I pass this ID to the detailed fragment?
Dashboard Fragment
class DashboardFragment : Fragment() {
private lateinit var viewModel: DashboardViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
// Get a reference to the binding object and inflate the fragment views.
val binding: DashboardFragmentBinding = DataBindingUtil.inflate(
inflater, R.layout.dashboard_fragment, container, false)
val application = requireNotNull(this.activity).application
val dataSource = NumberDatabase.getInstance(application).numberDatabaseDao
val viewModelFactory = DashboardViewModelFactory(dataSource, application)
// Get a reference to the ViewModel associated with this fragment.
val dashboardViewModel =
ViewModelProviders.of(
this, viewModelFactory).get(DashboardViewModel::class.java)
// To use the View Model with data binding, you have to explicitly
// give the binding object a reference to it.
binding.dashboardViewModel = dashboardViewModel
val adapter = CounterAdapter(CounterListener { nightId ->
dashboardViewModel.onCountClicked(nightId)
})
binding.counterList.adapter = adapter
dashboardViewModel.counts.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
}
Adapter
class CounterAdapter (val clickListener: CounterListener): ListAdapter<Number, CounterAdapter.ViewHolder>(NightDiffCallback()) {
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bind(getItem(position)!!, clickListener)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
class ViewHolder private constructor(val binding: ListItemCounterBinding) : RecyclerView.ViewHolder(binding.root){
fun bind(item: Number, clickListener: CounterListener) {
binding.night = item
binding.clickListener = clickListener
binding.executePendingBindings()
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ListItemCounterBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class CounterListener(val clickListener: (nightId: Long) -> Unit) {
fun onClick(night: Number) = clickListener(night.nightId)
}
In your recyclerview
class RecipeRecyclerAdapter(
mFragmentCommunication: FragmentCommunication
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mFragmentCommunication: FragmentCommunication? = null
init {
this.mFragmentCommunication =mFragmentCommunication
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int):
RecyclerView.ViewHolder {
view = LayoutInflater.from(parent.context)
.inflate(
com.broadcast.recipeslistapp.R.layout.layout_recipe_list_item,
parent,
false
)
return RecipeViewHolder(view)
}
}
override fun getItemCount(): Int {
if (mRecipes.isNullOrEmpty()) {
return 0
} else {
return mRecipes!!.size
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
holder.itemView.setOnClickListener {
mFragmentCommunication!!.PassNightId(nightId = 1) //value of nightId
}
}
interface FragmentCommunication {
fun PassNightId(nightId: Int)
}
Fragment A from where you want to send data :
class RecipeListActivity : BaseActivity(),
RecipeRecyclerAdapter.FragmentCommunication {
private lateinit var layoutManager: LinearLayoutManager
}
private fun initRecyclerView() {
mAdapter = RecipeRecyclerAdapter( this)
val itemDecorator = VerticalSpacingItemDecorator(30);
recipe_list.addItemDecoration(itemDecorator);
recipe_list.adapter = mAdapter
layoutManager = LinearLayoutManager(this);
recipe_list.layoutManager = layoutManager
}
override fun PassNightId(nightId: Int) {
val testFragment = TestFragment()
val bundle = Bundle()
bundle.putString("nightId", "$nightId")
testFragment.arguments = bundle
supportFragmentManager.beginTransaction().replace(R.id.containerId,testFragment).commit()
}
}
Fragment B where you want to getData :
class TestFragment : Fragment()
{
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val nightId =arguments?.getString("nightId")
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.test_fragment,container,false)
}
}
This is how you can send data from on fragment to another fragment.
Related
I have home fragment and I want it to go to another fragment which will show its "details", which passes the data of the clicked recyclerview item to that "details" fragment.
When I click the article it will move to detail article which passes the data.
As for the code, here's the adapter:
class ArticleAdapter(private val articleList: ArrayList<Article>) :
RecyclerView.Adapter<ArticleAdapter.MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
val binding = ItemArticleBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return MyViewHolder(binding)
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
val data = articleList[position]
holder.bind(data)
}
class MyViewHolder(private val binding: ItemArticleBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(data: Article) {
Glide.with(binding.root.context)
.load(data.img)
.into(binding.articleImage)
binding.articleTitle.text = data.title
binding.root.setOnClickListener {
val article = Article(
data.title,
data.img,
data.content
)
}
}
}
override fun getItemCount(): Int {
return articleList.size
}
}
Here's my detailFragment
class DetailArticleFragment : Fragment() {
private var _binding: FragmentDetailArticleBinding? = null
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentDetailArticleBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val intent = Intent(binding.root.context, DetailArticleFragment::class.java)
val article = intent.getParcelableExtra<Article>(DETAIL_ARTICLE) as Article
Glide.with(this)
.load(article.img)
.into(_binding!!.articleImage)
_binding!!.articleTitle.text = article.title
_binding!!.articleDescription.text = article.content
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
companion object {
const val DETAIL_ARTICLE = "detail_article"
}
}
You need to pass click listener interface to adapter, for example:
typealias OnArticleClick = (article: Article) -> Unit
class ArticleAdapter(
private val articleList: ArrayList<Article>
) :
RecyclerView.Adapter<ArticleAdapter.MyViewHolder>() {
var onArticleClick: OnArticleClick? = null
...
binding.root.setOnClickListener {
val article = Article(
data.title,
data.img,
data.content
)
onArticleClick?.invoke(article)
}
...
}
And init onArticleClick in your home fragment with RecyclerView:
adapter.apply {
onArticleClick = {
// show details fragment
}
}
i have defined a val in my fragment like so
private var selectionMode: Boolean = false
and a property to change it
fun enableSelectionMode() {
selectionMode = true
}
and i passed both to Adapter and want to call enableSelectionMode after clicking on each recycler item but nothing happen
here is my fragment:
class PostsFragment : Fragment() {
private val model: PostsViewModel by activityViewModels()
private var selectionMode: Boolean = false
fun enableSelectionMode() {
selectionMode = true
}
private lateinit var recyclerView: RecyclerView
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_posts, container, false)
recyclerView = view.findViewById(R.id.posts_recycler)
recyclerView.layoutManager = LinearLayoutManager(view.context)
model.posts.observe(viewLifecycleOwner, { items ->
recyclerView.adapter = PostsAdapter(items, model, selectionMode,
::enableSelectionMode)
})
return view
}
}
and my adapter
class PostsAdapter(
private val posts: List<Post>,
private val model: PostsViewModel,
private val selectionMode: Boolean,
private val enableIt: () -> Unit
) :
RecyclerView.Adapter<PostsAdapter.ViewHolder>() {
companion object {
private const val TYPE_FROM = 0
private const val TYPE_TO = 1
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var postContent: TextView = itemView.findViewById(R.id.post_content)
var selectPostCheckBox: ImageView = itemView.findViewById(R.id.post_select_checkbox)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(
if (viewType == TYPE_TO) R.layout.to_post else R.layout.from_post,
parent,
false
)
return ViewHolder(view)
}
#SuppressLint("CheckResult")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.selectPostCheckBox.visibility = if (selectionMode) View.VISIBLE else View.GONE
holder.postContent.text = selectionMode.toString()
holder.selectPostCheckBox.setImageResource(R.drawable.cursor_color)
holder.itemView.setOnClickListener() {
enableIt()
notifyDataSetChanged()
}
}
override fun getItemCount() = posts.size
override fun getItemViewType(position: Int): Int {
return posts[position].selfPost
}
}
I have a Data Repository that saved data & fetches data, this is passed into a ViewModel as constructor. I have methods in a ViewModel that fetches and saves data.
I have a button that I click for each row (Item in a RecyclerView list) this saves data using the ViewModel.
I have found that I can directly call a ViewModel initialised it into the constructor, I checked the Google Android examples & this part is not covered.
Something like this below:
Copied from: Databinding Recyclerview and onClick
private ExampleViewModel exampleViewModel;
public ExampleListAdapter(Context context, List<Model> models) {
this.context = context;
this.models = models;
// ...
exampleViewModel = ViewModelProviders.of((FragmentActivity) context).get(ExampleViewModel.class);
}
But then, I could also call a ViewModel by passing a ViewModel object from the Activity alongside with the context.
So what the proper way of calling a ViewModel?
This is your pojo class
data class Item(val id: Int)
This is your adapter.
class Adapter : RecyclerView.Adapter<Adapter.ViewHolder>() {
var items: List<Item> = emptyList()
set(value) {
field = value
notifyDataSetChanged()
}
var callback: Callback? = null
override fun getItemCount(): Int {
return items.size
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.simple_textview, parent, false)
return ViewHolder(itemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = items[position]
holder.itemView.setOnClickListener {
callback?.onItemClicked(item)
}
holder.bindItem(item)
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
fun bindItem(item: Item) {
// Fill layout
}
}
interface Callback {
fun onItemClicked(item: Item)
}
}
This is your viewModel class.
class MyViewModel : ViewModel(), Adapter.Callback {
override fun onItemClicked(item: Item) {
}
}
And this is your fragment.
class MyFragment : Fragment() {
private val adapter = Adapter()
private lateinit var myViewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
myViewModel = ViewModelProviders.of(activity!!).get(MyViewModel::class.java)
adapter.callback = myViewModel
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.my_fragment, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
adapter.items = listOf(
Item(1),
Item(2)
)
//Setup recyclerView etc.
}
}
I am using MVVM with Room persistence and livedata. I am fetching the data from local database and want to show in the form of list in the recyclerView and my recyclerView is not showing anything.
My adapter is like any other regular adapter
RecyclerView Code
class MyInformationAdapter : RecyclerView.Adapter<MyInformationAdapter.ViewHolder>() {
private var myList: List<PersonInformation> = ArrayList()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val view = layoutInflater.inflate(R.layout.my_adapter_data, parent, false)
return ViewHolder(view)
}
override fun getItemCount(): Int = myList.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
var myDataClass: PersonInformation = myList[position]
holder.name.text = myDataClass.name
holder.fName.text = myDataClass.fatherName
holder.email.text = myDataClass.email
holder.contact.text = myDataClass.contactNo.toString()
}
fun updateTheState(myList: List<PersonInformation>){
this.myList = myList
notifyDataSetChanged()
}
inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
var name: TextView = itemView.findViewById(R.id.yourName)
var fName: TextView = itemView.findViewById(R.id.yourFatherName)
var email: TextView = itemView.findViewById(R.id.yourEmail)
var contact: TextView = itemView.findViewById(R.id.yourContact)
}
}
RecyclerView Activity Code
class FinalActivity : AppCompatActivity() {
private var myDataList : List<PersonInformation> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_final)
setAdapter()
}
private fun setAdapter() {
val adapter = MyInformationAdapter()
val layoutManager = LinearLayoutManager(this)
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
adapter.updateTheState(myDataList)
}
}
*Fragment as a View of MVVM *
class PersonalInformationFragment : Fragment() {
private var viewModel: PersonInformationViewModel? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_personal_information, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
btn.setOnClickListener {
viewModel = ViewModelProviders.of(this)[PersonInformationViewModel::class.java]
viewModel?.getAllData()?.observe(this, Observer {
this.setAllData(it)
})
val intent = Intent(activity, FinalActivity::class.java)
startActivity(intent)
}
}
private fun setAllData(personInformation: List<PersonInformation>) {
val setData = PersonInformation(
name.text.toString(),
fName.text.toString(),
email.text.toString(),
contact.text.toString().toInt()
)
viewModel?.setTheData(setData)
}
}
I want to set an onClick Listener to the imageView present in the recycler view. But whenever I pass the imageview from onViewCreated() method from the fragment, it is still null and throws a NullPointerException when I invoke setOnClickListener.
These are kotlin classes.
class ShowDuesFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_show, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = Adapter(mutableListOf(..), fragmentmanager!!, imageView)
recycler_view.apply {
layoutManager = LinearLayoutManager(activity!!.applicationContext)
setHasFixedSize(true)
this.adapter = adapter
}
}
}
class Adapter(private val list: List<Due>, private val manager: FragmentManager, private val imageView: ImageView?) : RecyclerView.Adapter<ViewHolder> {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.card_view,
parent,
false
),
parent.context,
manager,
imageView
)
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.putData(list[position])
}
class ViewHolder(itemView: View, context: Context, manager: FragmentManager, imageView: ImageView? = null) :
RecyclerView.ViewHolder(itemView), DatePickerDialog.OnDateSetListener {
lateinit var item: Due
init {
Log.i("ViewHolder", (imageView == null).toString()) //log prints 'true'
imageView?.setOnClickListener {
val popup = PopupMenu(context, it)
popup.menuInflater.inflate(R.menu.menu_popup, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.notify -> {
val datePicker = DatePickerFragment()
datePicker.show(manager, "DatePickerFragment")
}
}
context.toast((item as Due).name)
true
}
popup.show()
}
}
fun putData(due: Due) {
…
item = due
}
…
}
The log message I get is true and when I click on the imageView, it does not respond to my clicks. How do I successfully implement an onClickListener to my imageView?
You are never initializing the imageView. As you are using ( ?.) while setting onClickListner to ImageView, you are not getting any crash due to safe call. One better way to do this is:
class ShowDuesFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_show, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = Adapter(mutableListOf(..), fragmentmanager!!)
recycler_view.apply {
layoutManager = LinearLayoutManager(activity!!.applicationContext)
setHasFixedSize(true)
this.adapter = adapter
}
}
}
class Adapter(private val list: List, private val manager: FragmentManager, private val imageView: ImageView?) : RecyclerView.Adapter {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.card_view,
parent,
false
),
parent.context,
manager
)
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.putData(list[position])
}
class ViewHolder(itemView: View, context: Context, manager: FragmentManager) :
RecyclerView.ViewHolder(itemView), DatePickerDialog.OnDateSetListener {
lateinit var item: Due
init {
val imageView = itemView //Cast this to image view if required you can use itemView.findViewById for other //views.
Log.i("ViewHolder", (imageView == null).toString()) //log prints 'true'
imageView?.setOnClickListener {
val popup = PopupMenu(context, it)
popup.menuInflater.inflate(R.menu.menu_popup, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.notify -> {
val datePicker = DatePickerFragment()
datePicker.show(manager, "DatePickerFragment")
}
}
context.toast((item as Due).name)
true
}
popup.show()
}
}
fun putData(due: Due) {
…
item = due
}
…
}
should work if you change onViewCreated to:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val adapter = Adapter(mutableListOf(..), fragmentmanager!!, view.imageView) // Specify which imageView you mean, as there might be a bunch of these fragments or none, system doesn't know.
or, do the whole view.findViewById() thingy if you're not using the kotlinx thingies
The ViewHolder class should be inside the Adapter as an inner class. By doing so, imageView is not null.
Something like this:-
class Adapter (
private val list: List<Due>,
private val manager: FragmentManager
) : RecyclerView.Adapter<Adapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
ViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.card_view,
parent,
false
),
parent.context
)
override fun getItemCount() = list.size
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.putData(list[position])
inner class ViewHolder(itemView: View, context: Context) :
RecyclerView.ViewHolder(itemView) {
private lateinit var item: Due
init {
Log.i("ViewHolder", (imageView == null).toString()) //log prints 'false'
itemView.dropdown_menu.setOnClickListener {
val popup = PopupMenu(context, it)
popup.menuInflater.inflate(R.menu.menu_popup, popup.menu)
popup.setOnMenuItemClickListener { menuItem ->
when (menuItem.itemId) {
R.id.notify -> {
val datePickerFragment = DatePickerFragment()
datePickerFragment.show(manager, "date picker")
}
}
true
}
popup.show()
}
}
fun putData(due: Due) {
…
}
}
}
And we instantiate the Adapter like:-
val adapter = Adapter(mutableListOf(…), fragmentmanager!!)