How to avoid Fragment reload data when pressing back from bottom navigation - android

Can anyone help me to solve the problem when fragment reload data every single tab back.
This is my MainActivity
class MainActivity : AppCompatActivity() {
private fun addBottomView(){
bottomNav.setOnNavigationItemSelectedListener(object: BottomNavigationView.OnNavigationItemSelectedListener{
override fun onNavigationItemSelected(p0: MenuItem): Boolean {
var selectedItem: Fragment? = null
when(p0.itemId){
R.id.words ->
selectedItem = FragmentA.newInstance()
R.id.writing ->
selectedItem = FragmentB.newInstance()
R.id.speaking ->
selectedItem = FragmentC.newInstance()
R.id.reading ->
selectedItem = FragmentD.newInstance()
}
var ft: FragmentTransaction = supportFragmentManager.beginTransaction()
ft.replace(R.id.frame_layout, selectedItem!!)
ft.commit()
return true
}
})
var ft: FragmentTransaction = supportFragmentManager.beginTransaction()
ft.replace(R.id.frame_layout, WordsFragment.newInstance())
ft.commit()
}
}
I have found on the internet and youtube videos but no one answered this questions precisely, they recommended to use add, hide instead of replace but not so sure how to do it. if possible, can u guys explain it as clearly as you can :)
This is my ViewPageAdapter
class ViewPagerAdapter: FragmentPagerAdapter {
private val mFragmentList: MutableList<Fragment> = mutableListOf()
private val mFragmentTitleList : MutableList<String> = mutableListOf()
constructor(manager: FragmentManager) : super(manager){
}
fun addFragment(fragment: Fragment, title: String){
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
override fun getItem(position: Int): Fragment {
return mFragmentList.get(position)
}
override fun getCount(): Int {
return mFragmentList.size
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList.get(position)
}
}
Thanks

You can solve this by defining the fragments as singleton. for example:
class FragmentA: Fragment {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
companion object {
private var instance: FragmentA? = null
fun newInstance(): FragmentA {
if (instance == null) instance = FragmentA()
return instance!!
}
}
}

Related

How to add another fragment into ViewPager2?

I'm trying to add more fragments dynamically inside my ViewPager2 however when I call the adapter.add(MyNewFragment, position) 2 things happens, if I use notifyDataSetChange() is not showing up the fragment but if I put notifyItemInserted(position) is showing up but the app crashes with an java.lang.IllegalStateException: Fragment already added.
This is my adapter class:
class ViewPagerAdapter(
list: MutableList<Fragment>,
fm: FragmentManager,
lifecycle: Lifecycle,
//fragmentActivity: FragmentActivity
) : FragmentStateAdapter(fm, lifecycle) {
var fragmentList = list
override fun getItemCount(): Int {
return fragmentList.size
}
fun addScreen(fragment: Fragment, position: Int) {
if(!fragmentList.contains(fragment)){
fragmentList.add(position, fragment)
//notifyItemInserted(position)
notifyDataSetChanged()
}
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
// I tried to use these methods that I saw here but then the viewpager is totally blank and does not load anything or do the same Fragment already added error.
/* override fun getItemId(position: Int): Long {
return fragmentList[position].id.toLong()
}
override fun containsItem(itemId: Long): Boolean = fragmentList.any { it.id.toLong() == itemId }*/
}
And this is my the screen where the viewPager is:
#AndroidEntryPoint
class ViewPagerScreen : Fragment() {
//...
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
if (!::savedViewInstance.isInitialized) {
_binding = FragmentViewPagerScreenBinding.inflate(inflater, container, false)
//....
savedViewInstance = binding.root
}
binding.viewPager.isSaveEnabled = false
return savedViewInstance
}
fun addNewScreen(){
adapter.addScreen(NewFragment(), binding.viewPager.currentItem + 1)
}
private fun initViewPager() {
binding.viewPager.offscreenPageLimit = 2
binding.viewPager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
}
})
adapter = ViewPagerAdapterByFragments(
mutableListOf(FirstScreen(), SecondScreen(), ThirdScreen()),
this#ViewPagerScreen.childFragmentManager,
lifecycle
)
binding.viewPager.adapter = adapter
binding.viewPager.isUserInputEnabled = false
}
}
I think I solved the problem, thanks #snachmsm for pointing me out where to look for.
What I did was quite simple, I only had to change the param list: MutableList<Fragment> to list:MutableList<Pair<String,Fragment>> where my String is my fragmentId. Then on the method createFragment return a new fragment base on my fragmentId.
Here's the code itself:
override fun createFragment(position: Int): Fragment {
return when (fragmentList!![position]!!.first) {
"firstScreen" -> return FirstScreen()
"secondScreen" ->return SecondScreen()
"thirdScreen" ->return ThirdScreen()
"newScreen" -> return NewScreen()
else -> AnotherScreen()
}
}
Also my addScreenMethod() now looks like this:
fun addScreen(newfragment: Pair<String, Fragment>, position: Int) {
val result = fragmentList.find {
it.first == newfragment.first
}
if (result == null) {
fragmentList.add(position, newfragment)
notifyItemInserted(position)
}
}

Responding to ViewPager2 Fragments Events in Activity

I have a main activity that contains a ViewPager2 and a TabLayout to house a few fragments. Each fragment will have buttons that I want to trigger actions in the main activity. However, I'm not seeing how to set up the buttons/event handlers so that the main activity is notified whenever a button is pressed inside one of the fragments. Thanks!
MenuActivity.kt - ViewPager2/TabLayout Setup
private fun configureMenu() {
// Create adapter
val fragmentAdapter = PageAdapter(supportFragmentManager, lifecycle)
// Add fragments
fragmentAdapter.addFragment(CameraFragment(), getString(R.string.camera_tab))
fragmentAdapter.addFragment(DevicesFragment(), getString(R.string.devices_tab))
fragmentAdapter.addFragment(ConnectionFragment(), getString(R.string.connection_tab))
// Set up pageviewer
viewpager_menu.adapter = fragmentAdapter
TabLayoutMediator(tablayout_main, viewpager_menu) { tab, position ->
tab.text = fragmentAdapter.getPageTitle(position)
viewpager_menu.setCurrentItem(tab.position, true)
}.attach()
}
PageAdapter.kt
class PageAdapter(fm: FragmentManager, lifecycle: Lifecycle): FragmentStateAdapter(fm, lifecycle) {
private var fragmentList = ArrayList<Fragment>()
private var fragmentTitleList = ArrayList<String>()
fun addFragment(fragment: Fragment, title: String) {
fragmentList.add(fragment)
fragmentTitleList.add(title)
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
}
override fun getItemCount(): Int {
return fragmentList.size
}
override fun createFragment(position: Int): Fragment {
return fragmentList[position]
}
fun getPageTitle(position: Int): String {
return fragmentTitleList[position]
}
}
CameraFragment.kt
class CameraFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_camera, container, false)
}
}
Thanks!
Inside actions of the fragment
val main = this.activity!! as MainActivity
main.callSomeFunc()
You can pass the this object of activity in specific frament constructor and use from fragment to call the activity class method
for example:
class MenuActivity : AppCompatActivity(){
fragmentAdapter.addFragment(CameraFragment(this), getString(R.string.camera_tab))
}
class CameraFragment : Fragment {
var mActivity: MenuActivity? = null
constructor(mActivity: MenuActivity?) {
this.mActivity = mActivity
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.fragment_camera, container, false)
onlick{
mActivity.callMethodOfMenuActivity()
}
}
}

Getting “java.lang.IllegalArgumentException: No view found for id” in ViewPager's Fragment that in RecyclerView

I am getting exception:
E/MessageQueue-JNI: java.lang.IllegalArgumentException: No view found for id 0x7f0a0554 (ua.com.company.app:id/vpBanners) for fragment BannerItemFragment{30698be} (4c80b228-4303-4c80-b99d-a55b8359b8c2) id=0x7f0a0554}
My hierarchy looks like so:
My adapter for vpHome:
class HomeViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val mFragmentList: MutableList<Fragment> = ArrayList()
private val mFragmentTitleList: MutableList<String> = ArrayList()
override fun getItem(position: Int): Fragment {
return mFragmentList[position]
}
override fun getCount(): Int {
return mFragmentList.size
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList[position]
}
fun addFragment(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
}
And I apply it in this way:
private fun setupViewPager(viewPager: DisableSwipeViewPager) {
vpAdapter = HomeViewPagerAdapter(childFragmentManager).apply {
addFragment(ForYouFragment(), "for you")
addFragment(AnotherFragment1(), "a1")
addFragment(AnotherFragment2(), "a2")
}
viewPager.adapter = vpAdapter
}
Next, my SnapBannersCarouselViewHolder to handle items with ViewPager inside:
class SnapBannersCarouselViewHolder(
private val mBinding: HomeFragmentItemSnapBannersCarouselBinding
) : RecyclerView.ViewHolder(mBinding.root) {
companion object {
// ...
fun newInstance(
inflater: LayoutInflater,
parent: ViewGroup,
onMoreInteractionListener: ((infoBlockId: String) -> Unit)?
): SnapBannersCarouselViewHolder {
val binding =
HomeFragmentItemSnapBannersCarouselBinding.inflate(inflater, parent, false)
return SnapBannersCarouselViewHolder(
binding,
onMoreInteractionListener
)
}
}
fun bind(item: SnapBannersItem, fragmentManager: FragmentManager) {
// ...
val adapter = BannerPagesAdapter(fragmentManager, item.banners)
with(mBinding.vpBanners) {
// ...
this.adapter = adapter
offscreenPageLimit = 3
}
}
}
My RecyclerView adapter ForYouContentAdapter:
class ForYouContentAdapter(
var data: List<HomeBaseItem> = emptyList(),
var fragmentManagerRetriever: () -> FragmentManager
) : BaseRecyclerViewAdapter<HomeBaseItem>(data) {
enum class ViewType(val value: Int) {
// ...
SNAP_BANNERS(6)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
// ...
ViewType.SNAP_BANNERS.value -> SnapBannersCarouselViewHolder.newInstance(
mInflater!!,
parent,
onBannersMoreInteractionListener // todo possibly change it
)
else -> throw RuntimeException("Can not create view holder for undefined view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
// ...
ViewType.SNAP_BANNERS.value -> {
val vHolder = holder as SnapBannersCarouselViewHolder
vHolder.bind(
getItem(position) as SnapBannersItem,
fragmentManagerRetriever.invoke()
)
}
}
}
}
And my fragmentManagerRetriever implementation in ForYouFragment looks like so:
private val fragmentManagerRetriever: () -> FragmentManager = {
childFragmentManager
}
My BannerItemFragment code:
class BannerItemFragment : Fragment() {
private lateinit var mBinding: HomeFragmentSnapBannerItemBinding
companion object {
// ...
fun newInstance(
item: RectWebViewItem
): BannerItemFragment {
return BannerItemFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBinding = HomeFragmentSnapBannerItemBinding.inflate(inflater, container, false)
return mBinding.root
}
// ...
}
In every place where I need to create fragments inside other fragments I am using childFragmentManager.
And when I open ForYouFragment first time, it works normally. My item with ViewPager works normally. But when I replace fragment in Activity's container (adding to back stack) being on ForYouFragment and then return back (back on HomeFragment because ForYouFragment inside HomeFragment), I am getting error.
To replace fragments I am using this method inside ForYouFragment:
private fun showAnotherFragment() {
ActivityUtils.replaceFragmentToActivity(
requireActivity(),
SomeFragment.newInstance(),
true
)
}
And ActivityUtils code:
object ActivityUtils {
fun replaceFragmentToActivity(
activity: FragmentActivity,
fragment: Fragment,
addToBackStack: Boolean
) {
replaceFragmentToActivity(activity, fragment, addToBackStack, containerId = R.id.fragmentContainer)
}
fun replaceFragmentToActivity(
activity: FragmentActivity,
fragment: Fragment,
addToBackStack: Boolean,
containerId: Int
) {
val fragmentManager = activity.supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(containerId, fragment)
if (addToBackStack) {
transaction.addToBackStack(null)
}
transaction.commitAllowingStateLoss()
}
}
Please, help me understand why I am getting this exception?
UPD
Adapter for ViewPager inside RecyclerView:
class BannerPagesAdapter(
fragmentManager: FragmentManager,
var data: List<RectWebViewItem>
) : FragmentStatePagerAdapter(
fragmentManager,
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {
private var fragments: MutableList<BannerItemFragment> = mutableListOf()
init {
// initial empty fragments
for (i in data.indices) {
fragments.add(BannerItemFragment())
}
}
override fun getItem(position: Int): Fragment {
return BannerItemFragment.newInstance(data[position])
}
override fun getCount(): Int {
return fragments.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val f = super.instantiateItem(container, position)
fragments[position] = f as BannerItemFragment
return f
}
}
Solved
The problem was that fragments want to be attached to the ViewPager before the ViewPager is attached to its parent. This question outlined here.
So, to solve this problem, I created custom ViewPager:
/**
* Use this ViewPager when you need to place ViewPager inside RecyclerView.
* [LazyViewPager] allows you to set [PagerAdapter] in lazy way. This prevents IllegalStateException
* in case when the fragments want to be attached to the viewpager before the viewpager is
* attached to its parent
*/
class LazyViewPager
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
private var mPagerAdapter: PagerAdapter? = null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (mPagerAdapter != null) {
super.setAdapter(mPagerAdapter)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
super.setAdapter(null)
}
#Deprecated("Do not use this method to set adapter. Use setAdapterLazy() instead.")
override fun setAdapter(adapter: PagerAdapter?) {}
fun setAdapterLazy(adapter: PagerAdapter?) {
mPagerAdapter = adapter
}
}
And then, instead of using setAdapter() I use setAdapterLazy().
Also, it is important to reset adapter to null in onDetachedFromWindow().

Kotlin Android Fragment recyclerView and context issue

I would like to create an recyclerView in a fragment, but it shows an error " java.lang.IllegalStateException: recylerView_Main must not be null
at com.gph.bottomnavigation.FragmentMe.onCreateView(FragmentMe.kt:28)"
Question 1) Please kindly help to solve this issue.
Question 2) I created an recyclerView only in a empty project without any fragment, it is working properly.
But the same code is no working in Fragment, it shows error so I change "recylerView_Main.layoutManager = LinearLayoutManager(this)" to "recylerView_Main.layoutManager = LinearLayoutManager(context)"
It shows no error and I can run in simlulator, but when I click the navigation button of the Fragment, the app stops and show this error. Please kindly help to solve it.
Here with the code for FragmentMe.kt:
class FragmentMe : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View? {
recylerView_Main.layoutManager = LinearLayoutManager(context)
recylerView_Main.adapter = Mainadapter()
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_me, container, false)
}
}
Here with the code of MainActivity.kt:
class MainActivity : AppCompatActivity() {
val manager = supportFragmentManager
private val mOnNavigationItemSelectedListener = BottomNavigationView.OnNavigationItemSelectedListener { item ->
when (item.itemId) {
R.id.navigation_home -> {
//message.setText(R.string.title_home)
createFragmentQpon()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_dashboard -> {
//message.setText(R.string.title_dashboard)
createFragmentMe()
return#OnNavigationItemSelectedListener true
}
R.id.navigation_notifications -> {
//message.setText(R.string.title_notifications)
createFragmentTools()
return#OnNavigationItemSelectedListener true
}
}
false
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Action Bar
val actionBar = supportActionBar
actionBar!!.setDisplayShowHomeEnabled(true)
actionBar.setBackgroundDrawable(ColorDrawable(Color.parseColor("#00FFFFFF")))
actionBar.setIcon(R.drawable.ic_home_black_24dp)
actionBar.setDisplayShowTitleEnabled(false)
createFragmentQpon()
navigation.setOnNavigationItemSelectedListener(mOnNavigationItemSelectedListener)
}
fun createFragmentQpon() {
val transaction = manager.beginTransaction()
val fragment = FragmentQpon()
transaction.replace(R.id.fragmentholder,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createFragmentMe() {
val transaction = manager.beginTransaction()
val fragment = FragmentMe()
transaction.replace(R.id.fragmentholder,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
fun createFragmentTools() {
val transaction = manager.beginTransaction()
val fragment = FragmentTools()
transaction.replace(R.id.fragmentholder,fragment)
transaction.addToBackStack(null)
transaction.commit()
}
}
Here with the code of Mainadapter.kt:
class Mainadapter: RecyclerView.Adapter<CustomViewHolder>() {
val videolist = listOf("aaa","bbbb","cccc")
override fun getItemCount(): Int {
return 3
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomViewHolder {
val layoutInflater = LayoutInflater.from(parent?.context)
val cellForRow = layoutInflater.inflate(R.layout.tutorial_layout, parent, false)
return CustomViewHolder(cellForRow)
}
override fun onBindViewHolder(holder: CustomViewHolder, position: Int) {
var videoName = videolist.get(position)
holder.itemView.title.text = videoName
}
}
class CustomViewHolder(v: View): RecyclerView.ViewHolder(v) {
}
Move this code
recylerView_Main.layoutManager = LinearLayoutManager(context)
recylerView_Main.adapter = Mainadapter()
from onCreateView to onActivityCreated
override onActivityCreated and place the above code.
There are two things incorrect in your code :
You are trying to access recyclerView even before inflating the View.
The context of a Fragment is null in onCreateView and is usable in between onAttach and onDetach
recylerView_Main.layoutManager = LinearLayoutManager(this.context)
Try this out, worked fine for me.

Fragment inside a viewpager doesnt show when is in recyclerview

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.

Categories

Resources