I have an adapter inside a fragment that belongs to an Activity A, from this adapter I open the detail of an element in an Activity B. How can I tell the adapter of Activity A that it has to update the list of the adapter that is in a fragment of Activity B?
This is the method I have inside the activity:
private fun setOnClickFavoriteListener(recipe: RecipesItem?) {
binding?.recipeDetailContainerFavoriteButton?.setOnClickListener {
if (recipe?.isFavorite == true) {
isFavorite = false
viewModel.setFavoriteRecipe(recipe.id, isFavorite)
binding?.recipeDetailImgFavoriteButton?.setColorFilter(
ContextCompat.getColor(
this,
R.color.silver
)
)
} else {
isFavorite = true
viewModel.setFavoriteRecipe(recipe?.id, isFavorite)
binding?.recipeDetailImgFavoriteButton?.setColorFilter(
ContextCompat.getColor(
this,
R.color.sunglo
)
)
}
}
}
And this is my adapter which is not in the same activity, so I have no communication between them:
class FavoritesAdapter(private val favorites: List<RecipesItem>) :
RecyclerView.Adapter<FavoritesViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavoritesViewHolder {
val binding = FavoritesRowBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return FavoritesViewHolder(binding)
}
override fun onBindViewHolder(holder: FavoritesViewHolder, position: Int) {
holder.bind(favorites[position])
}
override fun getItemCount(): Int {
return favorites.size
}
}
class FavoritesViewHolder(private val binding: FavoritesRowBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(recipesItem: RecipesItem) {
binding.favoritesRowImgRecipeImage.load(recipesItem.imageURL)
binding.favoritesRowLabelRecipeName.text = recipesItem.name
binding.favoritesRowLabelTime.text = binding.root.context.getString(
R.string.recipe_time,
recipesItem.time
)
binding.favoritesRowLabelDifficult.text = recipesItem.difficult
binding.root.setOnClickListener {
val bundle = bundleOf(RECIPE_BUNDLE to recipesItem)
val intent = Intent(binding.root.context, RecipeDetailActivity::class.java)
intent.putExtras(bundle)
binding.root.context.startActivity(intent)
}
}
}
I tried to put the fetching of the favourites list in the onResume, but it does a strange effect where it briefly shows the empty list image and then shows the updated list:
#AndroidEntryPoint
class FavoritesFragment : Fragment() {
private val viewModel: FavoritesViewModel by viewModels()
private var binding: FragmentFavoritesBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentFavoritesBinding.inflate(layoutInflater, container, false)
return binding?.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(activity as DashboardActivity).showToolbar(true)
(activity as DashboardActivity).setToolbarTitle(getString(R.string.option_menu_favorite))
}
private fun initObservers() {
viewModel.favoritesLiveData.observe(viewLifecycleOwner) { favorites ->
if (favorites.isNullOrEmpty()) {
binding?.favoritesRecyclerViewRecipes?.hide()
binding?.favoritesGroupEmptyRecipes?.show()
} else {
binding?.favoritesRecyclerViewRecipes?.show()
binding?.favoritesGroupEmptyRecipes?.hide()
val adapter = FavoritesAdapter(favorites)
binding?.favoritesRecyclerViewRecipes?.layoutManager = LinearLayoutManager(
context,
LinearLayoutManager.VERTICAL,
false
)
binding?.favoritesRecyclerViewRecipes?.adapter = adapter
}
}
}
override fun onResume() {
super.onResume()
viewModel.getFavoritesFromDatabase()
initObservers()
}
override fun onDestroy() {
binding = null
super.onDestroy()
}
From The adapter in Fragment of Activity 'A' you send the Item in the intent bundle as you did.
Then from Activity 'B' you handle this bundle and send the item to the Fragment in Activity 'B' then the fragment will render the item in its adapter.
I hope I understand your question right
Related
I want to pass data to fragment so when items in recycler adapter clicked it pass item name (sample[position].text1) fetched from firebase to fragment. I tried bundle, interface but getting error in both methods.I searched on internet but not find anything which solve my problem. mainActivity(splash screen) is only Activity in my App rest are fragments.
I used inner class method, I'm getting result but in another fragment where this adapter attached and I don't want it there.
Problem: pass sample[position].text1 to fragment so I can pass it to db.collection("here") to fetch data from Firebase.
Adapter
class dashboard_gridlayout_adapter(
private val sampledata: ArrayList<daxhboard_gridlayout_data>
): Adapter<dashboard_gridlayout_adapter.dashboard_viewholder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): dashboard_viewholder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.dashboard_gridlayout_single_item_design, parent, false)
return dashboard_viewholder(itemView)
}
override fun onBindViewHolder(holder: dashboard_viewholder, position: Int) {
Glide.with(holder.itemView).load(sampledata[position].imageResource)
.placeholder(R.drawable.ic_baseline_history_icon)
.into(holder.imageView)
holder.textView.text = sampledata[position].text1
holder.itemView.setOnClickListener {
val appCompatActivity = it.context as AppCompatActivity
appCompatActivity.supportFragmentManager.beginTransaction()
.replace(R.id.Activity_frag_container, service_providers_list())
.addToBackStack(null)
.commit()
}
}
override fun getItemCount() = sampledata.size
inner class dashboard_viewholder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val imageView: ImageView = itemView.dashboard_adapter_image
val textView: TextView = itemView.dashboard_adapter_text
}
}
Fragment
class service_providers_list : Fragment(){
private var db = FirebaseFirestore.getInstance()
private lateinit var service_list_recycler: RecyclerView
var servlist = ArrayList<service_provider_list_data>()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.service_providers_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
getserviceproviderdata()
service_list_recycler = service_provider_recycle_view.findViewById(R.id.service_provider_recycle_view)
service_provider_recycle_view.layoutManager = LinearLayoutManager(this.requireContext())
service_provider_recycle_view.setHasFixedSize(true)
}
private fun getserviceproviderdata() {
db.collection("Barber").orderBy("dist")
.get()
.addOnSuccessListener { documents ->
servlist.clear()
for (document in documents) {
val imgurl = document.data["imageResource"].toString()
val prov_name = document.data["provider_name"].toString()
val prov_address = document.data["provider_address"].toString()
val prov_rate = document.data["provider_rating"].toString()
val prov_dist = document.data["provider_distance"].toString()
servlist.add(service_provider_list_data(imgurl, prov_name, prov_address, prov_rate, prov_dist))
service_provider_recycle_view.adapter = service_provider_list_adapter(servlist)
}
}
.addOnFailureListener { exception ->
Log.e("serf", "Error getting documents: ", exception)
}
}
}
MainActivity (It's a splash screen)
class MainActivity : AppCompatActivity(){
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
#Suppress("DEPRECATION")
Handler().postDelayed(
{
supportFragmentManager.beginTransaction().replace(R.id.Activity_frag_container,Login_Screen()).commit()
},
1500
)
}
}
I solved this problem
just add new parameter(need to pass) inside replace in adapter
holder.itemView.setOnClickListener {
val datashares = sampledata[position].text1
val appCompatActivity = it.context as AppCompatActivity
appCompatActivity.supportFragmentManager.beginTransaction()
.replace(R.id.Activity_frag_container, service_providers_list(datashares))
.addToBackStack(null)
.commit()
}
and inside fragment just add
class service_providers_list(datashares: String) Fragment(){
//variable declaration
private var datasharae = datashares
(inside function where i wnt to add code i.e getserviceproviderdata() )
fun getserviceproviderdata() {
db.collection(datasharae)
.............
..............
......rest code.....
.........}
i am working with single activity and MVVM artitechture its have main activity for just launcher then there is the MainFragment from there we go to ViewPagerContainerFragment using navigation component and in that fragment i have viewpager2 setup with tablayout and in each fragment of viewpager2 there is recyclerview which need to load data from firebase so every thing working fine but the problem is when i click an item in recyclerview this open another fragment for detail info but then i come back i need to load all data back which look a little wired and some times recycler view positions chnaged so there is a way to retain viepager2 state if come back from detail fragment see code below for detail
ViewPager2Adapter.kt
class ViewPager2Adapter(viewPagerContainerFragment : ViewPagerContainerFragment)
: FragmentStateAdapter(viewPagerContainerFragment) {
override fun getItemCount() = 8
override fun createFragment(position: Int): Fragment = ItemsViewPagerFragment()
}
ItemsViewPagerFragment.kt
class ItemsViewPagerFragment : Fragment(), ItemsViewPagerFragmentListener{
private val itemsViewPagerFragmentViewModel: ItemsViewPagerFragmentViewModel by viewModels()
private lateinit var itemsAdapter: ItemsAdapter
private val refreshing by lazy { itemsViewPagerFragmentViewModel.refreshing }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = inflater.inflate(R.layout.itemsviewpager_fragment, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initSwipeRefreshLayout()
}
private fun initSwipeRefreshLayout() {
refreshing.value = false
refreshing.observe(viewLifecycleOwner) {
swipeRefreshLayout.isRefreshing = it
}
swipeRefreshLayout.setOnRefreshListener {
refreshData()
}
}
override fun onResume() {
super.onResume()
getData()
}
private fun getData() {
if (!::itemsAdapter.isInitialized) {
refreshing.value = true
initItemsAdapter()
}
showRecyclerView()
}
private fun initItemsAdapter() {
itemsViewPagerFragmentViewModel.getItemsLiveData()
itemsAdapter = ItemsAdapter(
emptyList(),
ItemsClickListener()
)
}
private fun refreshData() {
if (!::itemsAdapter .isInitialized) initItemsAdapter()
else itemsViewPagerFragmentViewModel.getItemsLiveData()
}
private fun showRecyclerView() {
if (isAdded) {
recyclerView.apply {
setHasFixedSize(true)
layoutManager =
if (DeviceOrientationUtil.isLandscape(requireContext()))
StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
else LinearLayoutManager(context)
adapter = itemsAdapter
viewPagerFragmentViewModel.itemsLiveData.observe(viewLifecycleOwner) {
itemsAdapter.updateData(it)
refreshing.value = false
}
}
}
}
}
private inner class ItemsClickListener : ItemsViewHolder.ItemsClickListener {
override fun onItemClick(item: Item) {
findNavController().navigate(R.id.action_itemsViewPagerFragment_to_itemDetailFragment)
}
}
}
ItemsViewPagerFragmentViewModel.kt
class ItemsViewPagerFragmentViewModel : ViewModel() {
private val itemsRepository = ItemsRepository()
val refreshing = MutableLiveData<Boolean>()
lateinit var itemsLiveData: ItemsLiveData
var isDataCached = false
fun getItemsLiveData() {
itemsLiveData = itemsRepository.getitemsLiveData()
}
}
Im currently working on an app which has a Settings Activity. In this Settings Activity there is a FrameLayout which loads a fragment containing a RecyclerView.
In the fragment, i have to pass to the RecyclerView adapter a listener, which is needed for the OnItemClick function.
I tried using context instead of this, but it doesnt work.
How do you correctly pass a listener to an Adapter?
Code:
FragmentSettings.kt
class FragmentSettingsMain : Fragment(), AdapterSettings.OnItemClickListener {
val settingsList = listOf(
DataItemsSettings(getString(R.string.look), getString(R.string.lookdescription), R.drawable.ic_colored_color_lens),
DataItemsSettings(getString(R.string.playing), getString(R.string.playingdescription), R.drawable.ic_colored_view_carousel),
DataItemsSettings(getString(R.string.images), getString(R.string.imagesdscription), R.drawable.ic_colored_image),
DataItemsSettings(getString(R.string.audio), getString(R.string.audiodescription), R.drawable.ic_colored_volume_up),
DataItemsSettings(getString(R.string.other), getString(R.string.otherdescription), R.drawable.ic_colored_shape),
DataItemsSettings(getString(R.string.about), getString(R.string.aboutdescription), R.drawable.ic_colored_info)
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? = inflater.inflate(R.layout.fragment_settings_main, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
rvSettings.apply {
layoutManager = LinearLayoutManager(activity)
adapter = AdapterSettings(settingsList, NEEDS LISTENER HERE)
}
}
override fun OnItemClick(position: Int) {
when(position) {
0 -> //Start new fragment here
1 -> //Start new fragment here
2 -> //Start new fragment here
3 -> //Start new fragment here
4 -> //Start new fragment here
5 -> this.startActivity(Intent(this, ActivityAbout::class.java))
}
}
}
AdapterSettings.kt
class AdapterSettings(
var settingsList: List<DataItemsSettings>,
var listener: OnItemClickListener
) : RecyclerView.Adapter<AdapterSettings.SettingsViewHolder>() {
inner class SettingsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), View.OnClickListener {
init {
itemView.setOnClickListener(this)
}
override fun onClick(p0: View?) {
val position : Int = adapterPosition
if (position != RecyclerView.NO_POSITION) {
listener.OnItemClick(position)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SettingsViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_settings, parent, false)
return SettingsViewHolder(view)
}
override fun getItemCount(): Int {
return settingsList.size
}
override fun onBindViewHolder(holder: SettingsViewHolder, position: Int) {
holder.itemView.apply {
rvTitle.text = settingsList[position].stringTitle
rvDescription.text = settingsList[position].stringDescription
rvIcon.setImageResource(settingsList[position].itemIcon)
}
}
interface OnItemClickListener {
fun OnItemClick(position: Int)
}
}
As you are in apply block to use this you need to add #FragmentSettingsMain while using this.
AdapterSettings(settingsList,this#FragmentSettingsMain)
Or You can use
val listener = this
rvSettings.apply{
AdapterSettings(settingsList,listener)
}
You can pass lambda to the adapter, call adapter.onAction = {}
I'm trying to show another fragment upon clicking a recyclerview item. I already added an onclicklistener, now there's a todo part on the fragment.kt, which I need to know how to have it navigate to a new fragment
As for the code, here's the adapter:
class dormAdapter(
private val dorms: List<Dorms>,
private val listener: RecyclerViewClickListener
) : RecyclerView.Adapter<dormAdapter.DormViewHolder>() {
override fun getItemCount() = dorms.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
DormViewHolder(
DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.layout_home,
parent, false
)
)
override fun onBindViewHolder(holder: DormViewHolder, position: Int) {
holder.recyclerviewDormBinding.dorm = dorms[position]
holder.recyclerviewDormBinding.buttonReserve.setOnClickListener {
listener.onRecyclerViewItemClick(holder.recyclerviewDormBinding.buttonReserve, dorms[position])
}
holder.recyclerviewDormBinding.layoutBox.setOnClickListener {
listener.onRecyclerViewItemClick(holder.recyclerviewDormBinding.layoutBox, dorms[position])
}
}
inner class DormViewHolder(
val recyclerviewDormBinding: LayoutHomeBinding
) : RecyclerView.ViewHolder(recyclerviewDormBinding.root)
}
Here's the Fragment on the landing page:
class HomeFragment : Fragment(), RecyclerViewClickListener {
private lateinit var factory: HomeViewModelFactory
private lateinit var viewModel: HomeViewModel
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_home, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val api = DormsAPI()
val repository = DormRepository(api)
factory = HomeViewModelFactory(repository)
viewModel = ViewModelProviders.of(this, factory).get(HomeViewModel::class.java)
viewModel.getDorms()
viewModel.dorms.observe(viewLifecycleOwner, Observer { dorms ->
recyclerViewDorms.also{
it.layoutManager = LinearLayoutManager(requireContext())
it.setHasFixedSize(true)
it.adapter = dormAdapter(dorms, this)
}
})
}
override fun onRecyclerViewItemClick(view: View, dorms: Dorms) {
when(view.id){
R.id.button_reserve -> {
**// TODO: Go to new account if not signed up, etc...**
Toast.makeText(requireContext(), "Reserve button clicked", Toast.LENGTH_LONG).show()
}
R.id.layoutBox -> {
**// TODO: Go to Dorm Details**
Toast.makeText(requireContext(), "Go to dorm details", Toast.LENGTH_LONG).show()
}
}
}
}
Anything else needed for this question will be followed up for later.
HUGE EDIT: Onclicklistener is now added. Project is now good for MVVM architecture.
1) Declare mOnclicklistener property in Adapter class
private val mOnClickListener: View.OnClickListener
init {
mOnClickListener = View.OnClickListener { v ->
Log.d("onclick of item")
}
}
2) In onBindViewHolder function setOnclick listener
with(holder.mView) {
setOnClickListener(mOnClickListener)
}
Maybe this is a stupid question, but this is ruining my day...
I have a recyclerview in a fragment
override fun setUpRecyclerView(pics: List<Pictures>) {
recyclerView.setHasFixedSize(true)
layoutManager = LinearLayoutManager(context)
recyclerView.setLayoutManager(layoutManager)
mAdapter = NewsAdapter(pics, childFragmentManager)
recyclerView.setAdapter(mAdapter)
}
the NewsAdapter is:
class NewsAdapter
(private val mDataset: List<Pictures>, private val fragmentManager: FragmentManager)
: RecyclerView.Adapter<NewsAdapter.ViewHolder>() {
class ViewHolder(v: View) : RecyclerView.ViewHolder(v) {
var authorTextView: TextView
var viewPager: ViewPager
var indicator: CircleIndicator
init {
authorTextView = v.findViewById<View>(R.id.tv_author) as TextView
viewPager = v.findViewById<View>(R.id.viewPager) as ViewPager
indicator = v.findViewById<View>(R.id.indicator) as CircleIndicator
}
}
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int): NewsAdapter.ViewHolder {
val v = LayoutInflater.from(parent.context)
.inflate(R.layout.item_preview, parent, false)
return ViewHolder(v)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val pictureList = mDataset.get(position)
holder.titleTextView.text = pictureList.title
holder.authorTextView.text = pictureList.author
showContent(pictureList.content, holder.viewPager, holder.indicator)
}
fun showContent(contentModel: List<ContentModel>, viewPager: ViewPager, indicator: CircleIndicator) {
if (contentModel.size <= 1) {
indicator.hide()
}
viewPager.adapter = GalleryContentAdapter(fragmentManager, contentModel)
indicator.setViewPager(viewPager)
}
override fun getItemCount(): Int = mDataset.size
}
the GalleryContentAdapter is:
class GalleryContentAdapter(fm: FragmentManager, val contentModels: List<ContentModel>) : FragmentPagerAdapter(fm) {
val galleryContentFactory: GalleryContentFactory = GalleryContentFactory()
override fun getItem(position: Int): Fragment = galleryContentFactory.getFragment(contentModels[position])
override fun getCount(): Int = contentModels.size
}
From now I am only handling a kind of content and the factory is so easy, but this is the code:
class GalleryContentFactory {
fun getFragment(contentModel: ContentModel): Fragment {
when (contentModel.type) {
"PICTURE" -> return PictureFragment.newInstance(contentModel.value)
}
return PictureFragment.newInstance(contentModel.value)
}
}
and the last is the PictureFragment:
class PictureFragment : NewsContentFragment() {
companion object {
val KEY = "PictureFragment.URL"
fun newInstance(url: String): NewsContentFragment {
val fragment = PictureFragment()
val bundle = Bundle()
bundle.putString(KEY, url)
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_news_gif, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
arguments?.getString(KEY).let {
view.image.loadUrl(it!!)
}
}
}
When I debug the code, I check that the line view.image.loadUrl(it!!) is being executed, but nothing is rendered.
The loadUrl method is a extended function of imageviews that loads an image using Glide like this
fun ImageView.loadUrl(url: String) {
GlideApp.with(context).load(url).into(this)
}
and is being called correctly.
I also tried with PagerStateAdapter and take into account that I am passing the ChildFragmentManager to the adapter of the ViewPager.
If you can help me, you will save my day. Thanks in advance
This doesn't work. The way to go is a recycler into a recycler. If you want simulate as a ViewPager, you may use https://github.com/rubensousa/RecyclerViewSnap with a MATCH_PARENT in the
child views.