Passing data between fragments via adapter (Kotlin Android) - android

Very new to coding ~1 month. Apologies if this is answered elsewhere, but spent hours looking through other similar questions but cant get it quite right. What: Trying to pass the position click data from fragment 1 (MainFragment) to fragment 2 (MapsFragment) to load the corresponding map. Goes through the adapter onclick method because I want to use RecyclerView on MainFragment. Project is buildable but crashes when I click an item on recyclerview (cant open map). Whats wrong????
Thanks ahead of time!!
EDIT: ANSWERED
Thank you everyone for comments. Was able to solve this by 1) changing lateinit var on mapsfragment to a regular var + nullable, and 2) changing my bundle line in the main fragment to
val bundle = bundleOf(EXTRA_USER_MAP to sports[position])
view.findNavController(.navigate(R.id.action_mainFragment_to_mapsFragment, bundle)
woohoo Kotlin!
Main Fragment:
const val EXTRA_USER_MAP = "EXTRA_USER_MAP"
private const val TAG = "MainFragment"
class MainFragment : Fragment() {
override fun onCreateView ... // code to inflate my fragment view
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val sports = generateData()
myMaps.layoutManager = LinearLayoutManager(activity)
myMaps.adapter = this.context?.let {
MapsAdapter(it, sports, object: MapsAdapter.OnClickListener {
override fun onItemClick(position: Int) {
Log.i(TAG, "onItemClick $position")
// When user taps on view in recyclerview, navigate to new fragment and opens corresponding map
val bundle = Bundle()
bundle.putSerializable(EXTRA_USER_MAP, sports[position])
view.findNavController().navigate(R.id.action_mainFragment_to_mapsFragment)
}
})
}
}
private fun generateData(): List<Sport> { ... // code for my data set
Maps Fragment
private const val TAG = "MapsFragment"
class MapsFragment : Fragment(), OnMapReadyCallback {
private lateinit var map: GoogleMap
private lateinit var sport: Sport
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_maps, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val bundle = arguments
if (bundle !=null) {
sport = bundle.getSerializable(EXTRA_USER_MAP) as Sport
}
val mapFragment = childFragmentManager.findFragmentById(R.id.map) as SupportMapFragment?
mapFragment?.getMapAsync(this)
}
// Actions for map
override fun onMapReady(googleMap: GoogleMap) {
map = googleMap
Log.i(TAG, "Map rendered: ${sport.title}") // Logcat check once working
...
Related Adapter code
class MapsAdapter(
val context: Context,
val sports: List<Sport>,
val onClickListener: OnClickListener)
: RecyclerView.Adapter<MapsAdapter.ViewHolder>() {
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
interface OnClickListener {
fun onItemClick(position: Int)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val sport = sports[position]
holder.itemView.setOnClickListener {
Log.i(TAG, "Tapped on position $position")
onClickListener.onItemClick(position)
}
...

You can have a shared view model where you can have a LiveData property and you can change his value from MainFragment and listen to his changes from MapsFragment and do everything you want or you can have a listener and can listen when user click and get the position. Hope this will helpful for you

Related

Using Flow, the list is not reloaded in the recyclerView

I am training with a simple app to show movies, I use an MVVM pattern and Flow.
Problem
This is my home, filterable through chips
I click on a movie , the details screen comes up then I go back to the home and this is the result:
Using logcat the home screen gets the list of movies to show but is not shown in the recyclerview (which uses diffUtil).
Below is the code for my fragment:
#AndroidEntryPoint
class Home2Fragment : Fragment() {
private val TAG = Home2Fragment::class.simpleName
private var _binding: FragmentHome2Binding? = null
private val binding: FragmentHome2Binding
get() = _binding!!
private val viewModel: HomeViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// Inflate the layout for this fragment
_binding = FragmentHome2Binding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.apply {
initChipGroupSpecificMovieList()
val adapter = MovieAdapter()
sectionRv.setHasFixedSize(true)
sectionRv.adapter = adapter
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.movieListBySpecification.collectLatest {
Log.d(TAG, "onViewCreated: received list")
adapter.addItems(it)
}
}
}
}
}
private fun FragmentHome2Binding.initChipGroupSpecificMovieList() {
val sortByMap = HomeViewModel.Companion.MovieListSpecification.values()
chipGroup.removeAllViews()
for (specification in sortByMap) {
val chip = Chip(context)
chip.isCheckable = true
chip.id = specification.ordinal
chip.text = getString(specification.nameResource)
chip.setOnCheckedChangeListener { _, isChecked ->
if (isChecked)
viewModel.setMovieListSpecification(specification)
}
chipGroup.addView(chip)
}
chipGroup.check(sortByMap.lastIndex - sortByMap.size + 1)//check first element
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
it seems at the line of code where I try to insert the list of movies in the adapter this doesn't add them because maybe via diffUtil it finds that it is the previous list and so it doesn't load it. However it doesn't show the previous one either, possible solutions?
as java code you can use this
#Override
public void onResume() {
super.onResume();
if(it.size() > 0) {
adapter.addItems(it)
}
}

Passing adapter text to fragment creating problems

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.....
.........}

LiveData + ViewModel States , values clash and only last value is observed

Im using ViewModel with states and LiveData with one observer .
I have an activity and a fragment . everything works fine until Im rotating the screen
and then, the states I want to observe , clash.
What can I do in order to prevent it and make it work as expected?
I know I can add more observers but I don't want to solve it in this way , it may lead to problems with the other code.
MainActivity code :
private var appsDetailsHmap = HashMap< String , AppsDetails>()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
progressBar = CircleProgressBarDialog(this)
viewModel.getState().observe(this, Observer {state->
when(state){
is SettingsViewModelStates.GetAppsDetails->initUI(state.list)
is SettingsViewModelStates.ShowDialog->progressBar.showOrHide(state.visibility)
is SettingsViewModelStates.GetCachedData->setCachedSettings(state.appDetailsHmap,state.selectedApp,state.speechResultAppName)
}
})
if (savedInstanceState==null){
viewModel.initSettingsActivityUI(appsDetailsHmap)
}
else{
viewModel.initCachedSettingsActivityUI()
}
Fragment code
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
val view= inflater.inflate(R.layout.fragment_added_apps, container, false)
viewModel.getCachedApplist()
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel.getState().observe(viewLifecycleOwner, Observer {state->
when(state){
is SettingsViewModelStates.PassAppsToFragment->{
initRecyclerView(state.list)
}
}
})
}
ViewModel
private var state=MutableLiveData<SettingsViewModelStates>()
private var appsDetailsHmap=HashMap<String,AppsDetails>()
private var addedApps= HashMap<String,Drawable?>()
fun getState()=state as LiveData<SettingsViewModelStates>
fun getCachedApplist(){
if (addedApps.isEmpty()){
getAppsList()
println("empty")
}
else
state.setValue(SettingsViewModelStates.PassAppsToFragment(addedApps))
}
fun initCachedSettingsActivityUI(){
state.setValue(SettingsViewModelStates.GetCachedData(appsDetailsHmap,selectedApp,speechResultAppName))
}
fun initSettingsActivityUI(list:HashMap<String, AppsDetails>) {
appsDetailsHmap=list
state.setValue( SettingsViewModelStates.GetAppsDetails(list))
}
states:
sealed class SettingsViewModelStates {
data class GetAppsDetails(val list:HashMap<String, AppsDetails>):SettingsViewModelStates()
data class ShowDialog(val visibility: Int) : SettingsViewModelStates()
data class PassAppsToFragment(val list:HashMap<String,Drawable?>) : SettingsViewModelStates()
data class GetCachedData(val appDetailsHmap:HashMap<String,AppsDetails>,
val selectedApp: AppsDetails,
val speechResultAppName:String ) : SettingsViewModelStates()
}

How to properly map shared element transition from RecyclerView to ViewPager dynamically?

I have an activity that hosts both fragments called SourceFragment and DestinationFragment. The SourceFragment contains a RecyclerView and the DestinationFragment contains a ViewPager. I've been using the fragment manager to swap back and forth between the Source and Destination fragment.
Issue:
The return transition works normally as long as I don't swipe to a different view on the ViewPager. To resolve this I overrode the onMapSharedElements in the DestinationFragment and the SourceFragment to make sure the views match each other when the view pager is swiped.
For some reason swiping and returning to the SourceFragment doesn't work and no transition animation happens. I even debugged the onMapSharedElements functions to make sure that the views mapped correctly.
This is what I'm trying to implement.
My implementation
I've provided the code below for the fragments in question but here is the repo to my implementation. Been itching my head for a week now trying to figure this out hopefully I can get some insight as to why this is happening.
SourceFragment:
class SourceFragment : Fragment() {
private lateinit var sharedViewModel: SharedViewModel
private lateinit var itemRecyclerView: RecyclerView
private lateinit var adapter: SourceAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setExitSharedElementCallback(object : SharedElementCallback() {
override fun onMapSharedElements(names: MutableList<String>?, sharedElements: MutableMap<String, View>?) {
val view = itemRecyclerView.findViewHolderForAdapterPosition(sharedViewModel.currentPos)
?.itemView?.findViewById<TextView>(R.id.source_text)
val item = sharedViewModel.itemList[sharedViewModel.currentPos]
if (view == null) return
names?.clear()
sharedElements?.clear()
names?.add(item)
sharedElements?.put(item, view)
}
})
sharedViewModel = ViewModelProviders.of(requireActivity()).get(SharedViewModel::class.java)
adapter = SourceAdapter(object : onClickItem {
override fun onClick(position: Int, view: View) {
// Save the position of the item that was clicked.
sharedViewModel.currentPos = position
// Setup shared element transition
val transitionName = ViewCompat.getTransitionName(view) ?: ""
// Start fragment transaction along with shared element transition.
fragmentManager?.apply {
beginTransaction()
.setReorderingAllowed(true)
.addSharedElement(view, transitionName)
.replace(
R.id.fragment_container,
DestinationFragment(),
DestinationFragment::class.java.simpleName
)
.addToBackStack(null)
.commit()
}
}
})
val list = sharedViewModel.itemList
adapter.submitList(list)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.source_layout, container, false)
postponeEnterTransition()
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
// Setup recycler view here
itemRecyclerView = item_recycler_view
itemRecyclerView.layoutManager = GridLayoutManager(requireContext(), 2)
itemRecyclerView.adapter = adapter
// Start enter transition on pre draw.
itemRecyclerView.viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
itemRecyclerView.viewTreeObserver.removeOnPreDrawListener(this)
startPostponedEnterTransition()
return true
}
})
// Scroll to the position of the item that was selected in the Destination Fragment.
itemRecyclerView.addOnLayoutChangeListener { p0, p1, p2, p3, p4, p5, p6, p7, p8 ->
val layoutMan = itemRecyclerView.layoutManager
val viewAtPos = layoutMan?.findViewByPosition(sharedViewModel.currentPos)
if (viewAtPos == null ||
layoutMan.isViewPartiallyVisible(viewAtPos, false, true)
) {
itemRecyclerView.post {
layoutMan?.scrollToPosition(sharedViewModel.currentPos)
}
}
}
}
DestinationFragment
class DestinationFragment : Fragment(), MainActivityListener {
lateinit var sharedViewModel: SharedViewModel
lateinit var destPager : ViewPager
lateinit var itemAdapter : ItemPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
postponeEnterTransition()
setEnterSharedElementCallback(object : androidx.core.app.SharedElementCallback(){
override fun onMapSharedElements(names: MutableList<String>?, sharedElements: MutableMap<String, View>?) {
val item = sharedViewModel.itemList[destPager.currentItem]
val itemView = (itemAdapter.instantiateItem(destPager, destPager.currentItem) as Fragment).view?.findViewById<TextView>(R.id.item_fragment_textview) as View
names?.clear()
names?.add(item)
sharedElements?.clear()
sharedElements?.put(item, itemView)
}
})
sharedElementEnterTransition = TransitionInflater.from(requireContext()).inflateTransition(android.R.transition.move)
sharedViewModel = ViewModelProviders.of(requireActivity()).get( SharedViewModel::class.java)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.destination_layout, container ,false)
}
override fun onBackPressed(): Boolean {
sharedViewModel.currentPos = destPager.currentItem
return false
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
destPager = destination_pager
itemAdapter = ItemPagerAdapter(sharedViewModel.itemList, childFragmentManager)
destination_pager.adapter = itemAdapter
destination_pager.currentItem = sharedViewModel.currentPos
}

dialog containing ViewPager

I want to create a dialog which contain's ViewPager inside it which have 3 pages and all pages have different layout structure. I want a solution by that i can set the layout content programmatically . I think this can be done by making fragments for each page but i don't know how to do this.
I go through these answers but i am not getting idea how to use them in my case.
Viewpager in Dialog?
ViewPager in Custom Dialog
ViewPager in simple Dialog
You can try and build your custom dialog through DialogFragment. Consider the XML layout would contain a ViewPager and the code to go about would be:
class PagerDialog : DialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.element_fragment_pager_dialog, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setupPager()
}
private fun setupPager() {
val pagerFragment1 = PagerFragment1.newInstance()
val pagerFragment2 = PagerFragment2.newInstance()
val pagerFragment3 = PagerFragment3.newInstance()
viewPager?.adapter = MyFragmentPagerAdapter(childFragmentManager).apply {
adapterReference = object : PageAdapterInterface {
override var fragmentList: List<Fragment> =
mutableListOf(pagerFragment1, pagerFragment2, pagerFragment3)
}
}
}
companion object {
const val tag = "PagerDialog"
}
}
I have used reference to the list because it might cause leaks when not handled correctly. So the PagerAdapterInterface would look like:
interface PageAdapterInterface {
var fragmentList: List<Fragment>
fun getItemCount() = fragmentList.size
#Throws(StackOverflowError::class)
fun getItemAt(index: Int) : Fragment {
if (index >= fragmentList.size) throw StackOverflowError()
return fragmentList[index]
}
}
Your view pager adapter can make use of this reference in manner that is accessing referentially like:
class MyFragmentPagerAdapter(childFragmentManager: FragmentManager) : FragmentStatePagerAdapter(childFragmentManager){
lateinit var adapterReference: PageAdapterInterface
override fun getItem(p0: Int): Fragment = adapterReference.getItemAt(p0)
override fun getCount(): Int = adapterReference.getItemCount()
}
Finally in your Activity or Fragment on create() or onViewCreated() functions respectively, you can initialize the dialog as shown:
class MyActivity: AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
// use childFragmentManager if the code is
// used within the Fragment
val prev = supportFragmentManager.findFragmentByTag(PagerDialog.tag)
if (prev != null) {
supportFragmentManager.beginTransaction()
.remove(prev)
.addToBackStack(null)
.commit()
}
PagerDialog().show(supportFragmentManager, PagerDialog.tag)
}
}
Note: DialogFragment is deprecated on > API 28 check out https://developer.android.com/reference/android/app/DialogFragment

Categories

Resources