I use view-pager2 and When I remove the first item of view-pager, It does not delete and still remains but another item removed.
For example, here I want to remove the first item of view pager but it's not be removed:
My Main Activity:
class MainActivity : AppCompatActivity() {
private lateinit var myPagerAdapter: MyPagerAdapter
private val mFragmentList: ArrayList<FragmentOne> = ArrayList()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (mFragmentList.isEmpty()) {
mFragmentList.add(FragmentOne.newInstance(1))
mFragmentList.add(FragmentOne.newInstance(2))
mFragmentList.add(FragmentOne.newInstance(3))
}
myPagerAdapter = MyPagerAdapter(supportFragmentManager, lifecycle)
view_pager.adapter = myPagerAdapter
btn_show.setOnClickListener {
val dialog = Dialog(this)
dialog.setContentView(R.layout.fragment_dialog)
dialog.btn_remove.setOnClickListener {
//go to next item
view_pager.currentItem = 1
//remove first item
mFragmentList.removeAt(0)
//reload adapter
myPagerAdapter.notifyDataSetChanged()
}
dialog.show()
}
}
private inner class MyPagerAdapter(fragmentManager: FragmentManager, lifecycle: Lifecycle) :
FragmentStateAdapter(fragmentManager, lifecycle) {
override fun getItemCount(): Int {
return mFragmentList.size
}
override fun createFragment(position: Int): Fragment {
return mFragmentList[position]
}
}
}
FragmentOne:
class FragmentOne : Fragment() {
companion object {
fun newInstance(position: Int): FragmentOne {
val fragment = FragmentOne()
val bundle = Bundle()
bundle.putInt("id", position)
fragment.arguments = bundle
return fragment
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_one, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val id = arguments?.getInt("id")
tv?.text = id.toString()
}
}
If I don't use view-pager2, Just enough to add PagerAdapter.POSITION_NONE like this answer.
Can you help me?!
If you read the documentation it says
Note: The DiffUtil utility class relies on identifying items by ID. If you are using ViewPager2 to page through a mutable collection, you must also override getItemId() and containsItem().
Taken from this post - Migrate to ViewPager2
Since you are using a mutable ArrayList() you should also override the getItemId() method in your adapter, for the itemId you need a unique ID for every fragment, I solved it using the hashCode() function like this
override fun getItemId(position: Int): Long {
return mFragmentList[position].hashCode().toLong()
}
This should remove the first fragment and get the correct fragment from the list using the itemId
Hope this solves your problem.
Actually delete functionality is working properly 0th index fragment is deleted but after calling notfiydataset changed the first fragment gets th value as 1 and second as 2.
I did the same for java, with viewpager2 and its works!
#Override
public long getItemId(int position) {
Long longtype = Long.valueOf(mFragmentList.get(position).hashCode());
return longtype;
}
#Override
public boolean containsItem(long itemId) {
return super.containsItem(itemId);
}
Related
In my activity I am setting ViewPager,TabLayout and adding two fragment instance in a list. Then i am passing that list to ViewPagerAdapter .Responsibility of fragment is to fetch data from api call and show it in a list .
I am taking two instance of fragment because api returns two list of data that need to be show in tab fashion(One list in one tab and one in another). But when viewpager adapter returns fragment , if one data list is empty then I am getting empty screen in Tab-0 .
How to dynamically detect data size (here confused , because need to call fragment) and populate tab based on that.
ActivityOne.kt
class ActivityOne : BaseActivity() {
lateinit var item: ArrayList<HistoryTabItem>
lateinit var tabLayout: Tabs
val InfoViewpagerAdapter:InfoVIewPagerAdapter by lazy { InfoVIewPagerAdapter(supportFragmentManager, ArrayList()) }
fun newInstance(context: Context): Intent {
return Intent(context, InfoFragment::class.java)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.info_tabview)
getFragments()
InfoViewpagerAdapter.arrayList = item
tabLayout = findViewById(R.id.tabsnfo_type)
val viewPager = findViewById<ViewPager>(R.id.view_pager_info_type)
viewPager.adapter = InfoViewpagerAdapter
tabLayout.setupWithViewPager(viewPager)
}
fun getFragments() {
item = ArrayList()
val HistoryTabItemSeller = HistoryTabItem()
HistoryTabItemSeller.fragment = InfoFragment.createInstance()
item.add(HistoryTabItemSeller)
val HistoryTabItemBuyer = HistoryTabItem()
HistoryTabItemBuyer.fragment = InfoFragment.createInstance()
item.add(HistoryTabItemBuyer)
}
}
InfoViewPageradapter
class InfoVIewPagerAdapter(fm: FragmentManager, var arrayList: ArrayList<HistoryTabItem>) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
return arrayList[position].fragment
}
override fun getCount(): Int {
return arrayList.size
}
}
Fragment
class InfoFragment : BaseDaggerFragment(), InfoContract.View {
var isTickerShow: Boolean? = false
var tickerMessage: String? = null
lateinit var allTransactionList: ArrayList<Any>
#Inject
lateinit var infoPresenter: HoldInfoPresenter
val infoAdapter: InfoAdapter by lazy {
InfoAdapter(ArrayList()) }
lateinit var fakelist: ArrayList<Any>
companion object {
fun createInstance(): Fragment {
return InfoFragment()
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_container_info, container, false)
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
infoPresenter.attachView(this)
infoPresenter.getInfo()
}
fun initView() {
rv_container.layoutManager = LinearLayoutManager(context)
rv_container.adapter = infoAdapter
}
override fun renderInfo(depositHistory: DepositHistory?) {
var resultList = ArrayList<Any>()
depositHistory?.let {
resultList = combinedTransactionList(it.sellerData as ArrayList<SellerDataItem>, it.buyerData as ArrayList<BuyerDataItem>)
isTickerShow = it.tickerMessageIsshow
tickerMessage = it.tickerMessageId
}
infoAdapter.list.clear()
infoAdapter.list.addAll(resultList)
infoAdapter.notifyDataSetChanged()
}
fun combinedTransactionList(arrayList: ArrayList<SellerDataItem>, arrayList1: ArrayList<BuyerDataItem>): ArrayList<Any> {
allTransactionList = ArrayList()
allTransactionList.clear()
allTransactionList.addAll(arrayList)
allTransactionList.addAll(arrayList1)
return allTransactionList
}
}
The best option is to fetch data in the activity and then show that tab layout with 2 tabs or just one tab. You would use it with a shared view model, but I see you don't use view models here.
You can also just set the list in createInstance() method in Fragment when it isn't empty.
The third option is to fetch data in Fragment and then send information to activity that the list is empty and hide the specific tab.
i have a fragment with a BottomNavigationView, a Spinner and a FrameLayout, in the FrameLayout appears a a new fragment with the BottomNavigationView.setOnNavigationItemSelectedListener, this is my code:
Fragment ValcuotaEvolFragment
class ValcuotaEvolFragment: Fragment() {
lateinit var fragment : Fragment
override fun onCreateView(inflater: LayoutInflater,container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_valcuota_evol, container, false)
val menuBottom: BottomNavigationView = root.findViewById(R.id.nav_view_valcuota_evol)
val spn : Spinner = root.findViewById(R.id.spnAFP)
val db = DataBaseHandler(activity!!.applicationContext)
val afpListName : ArrayList<String> = db.getAFPNames()
fragment= ValcuotaChartFragment()
val bundle = Bundle()
spn.adapter= ArrayAdapter<String>(activity!!.applicationContext,android.R.layout.simple_spinner_dropdown_item,afpListName)
spn.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
bundle.putString("afp",spn.selectedItem.toString())
}
override fun onNothingSelected(parent: AdapterView<*>) { }
}
menuBottom.setOnNavigationItemSelectedListener {
menuItem ->
when(menuItem.itemId){
R.id.nav_evolcuota_chart -> {
fragment = ValcuotaChartFragment()
}
R.id.nav_evolcuota_data -> {
fragment = ValcuotaDataFragment()
}
}
fragment.setArguments(bundle);
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.frame_valcuota_evol, fragment)
transaction.addToBackStack(null)
transaction.commit()
true
}
fragment.setArguments(bundle);
val transaction = childFragmentManager.beginTransaction()
transaction.replace(R.id.frame_valcuota_evol, fragment)
transaction.addToBackStack(null)
transaction.commit()
return root
}
}
I pass to the new fragment the value "afp" through a Bundle, now i need the new fragment to do something different depending on what I select in the spinner of ValcuotaEvolFragment
this is what i need:
class ValcuotaDataFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val root = inflater.inflate(R.layout.fragment_valcuota_data, container, false)
val afp = arguments!!.getString("afp")
if(afp == "something"){
doSomething()
else {
doSomethingElse
}
return root
}
}
this actually works, but only when i change the item in the BottomNavigationView i need this works when change the item in the Spinner, thx
EDIT
The EventBus solution works fine , but now i have a new problem in ValcuotaDataFragment i have a RecyclerView, so now i need fill the RecyclerView after change the item in the Spinner, this is how i do it now:
val rcViewValcuota = root. findViewById(R.id.rc_valcuota_data) as RecyclerView
var valcuota : MutableList<ValcuotaModel>
val db = DataBaseHandler(activity!!.applicationContext)
valcuota = db.getCompleteValCuota(spinnerData.selectedItem,"desc")
rcViewValcuota.adapter= ContentValcuotaMonthlyAdapter(valcuota)
i can't access the "root" from the function listenItemChange
Okay, so when you're selecting a different item from the spinner, your fragment is not aware of it unless you pass data to fragment. So for informing the fragment, you can implement the interface if you'd like. Or my favorite you can use EventBus library to pass the data.
I'll show you how you can implement EventBus.
First, add EventBus Dependency is the Gradle file:
implementation 'org.greenrobot:eventbus:3.1.1'
Okay now create a data class for passing data say SpinnerData:
data class SpinnerData(val selectedItem:String)
Then inside your itemSelected listener, pass the data using EventBus:
spn.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>, view: View, position: Int, id: Long) {
// bundle.putString("afp",spn.selectedItem.toString())
//insted add following line
EventBus.getDefault().post(SpinnerData(spn.selectedItem.toString()))
}
override fun onNothingSelected(parent: AdapterView<*>) { }
}
Then inside your ValcuotaDataFragment add the following:
#Subscribe
fun listenItemChange(spinnerData: SpinnerData){
if (spinnerData.selectedItem == "something") {
doSomething()
} else {
doSomethingElse()
}
}
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
}
override fun onStop() {
EventBus.getDefault().unregister(this)
super.onStop()
}
Now, whenever you change the spinner item Evenbus will be triggered and pass the data to the Subscribed method inside your fragment.
Hope this helps, let me know if you get stuck somewhere.
Edit:
This won't work if your fragment isn't initialized already.
SO keep your line inside your itemSelected listener for first time use:
bundle.putString("afp",spn.selectedItem.toString())
i have an issue with an app that use ViewPager for display fragment. All works fine until the app goes in background and be killed from OS. It seems that after restore i have 2 IncidentScreenFragment that handle events, one with a null presenter (MVP) that crash my app.
My HomeActivity looks like:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
presenter.onViewCreated()
initViews(savedInstanceState)
}
private fun initViews(savedInstanceState: Bundle?){
mapView.onCreate(savedInstanceState)
mapView.getMapAsync(this)
initFragment()
initMenu()
}
private fun initFragment(){
homeFragment = HomeScreenFragment.newInstance()
incidentFragment = IncidentScreenFragment.newInstance()
chatFragment = ChatFragment.newInstance()
weatherFragment = WeatherFragment.newInstance()
viewPager.adapter = ViewPagerAdapter(supportFragmentManager, this)
viewPager.offscreenPageLimit = 4
viewPager?.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {
override fun onPageScrollStateChanged(state: Int) {}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
override fun onPageSelected(position: Int) {bottom_navigation.currentItem = position}
})
}
override fun getFragmentByPos(pos: Int): Fragment {
return when(pos){
0 -> homeFragment
1 -> incidentFragment
2 -> chatFragment
3 -> weatherFragment
else -> {
homeFragment
}
}
}
And my Adapter:
class ViewPagerAdapter internal constructor(fm: FragmentManager, activity:infinite_software.intelligence_center.intelligencecenter.ui.home.FragmentManager) : FragmentPagerAdapter(fm) {
private val COUNT = 4
private val activity = activity
override fun getItem(position: Int): Fragment{
var fragment: Fragment? = null
when (position) {
0 -> fragment = activity.getFragmentByPos(0)
1 -> fragment = activity.getFragmentByPos(1)
2 -> fragment = activity.getFragmentByPos(2)
3 -> fragment = activity.getFragmentByPos(3)
}
return fragment!!
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
super.destroyItem(container, position, `object`)
}
override fun getCount(): Int {
return COUNT
}
override fun getPageTitle(position: Int): CharSequence? {
return "Section " + (position + 1)
}
}
Each Fragment have a static method that return new Fragment:
companion object {
fun newInstance(): HomeScreenFragment {
return HomeScreenFragment()
}
}
When the app has been killed in background i figure out that there is 2 objects (Fragment) that listen to event, one with Presenter correctly instantiate and one without.
Below my abstract BaseFragment class:
abstract class BaseFragment<P : BasePresenter<BaseView>> : BaseView,Fragment() {
protected lateinit var presenter: P
override fun getContext(): Context {
return activity as Context
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return super.onCreateView(inflater, container, savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
presenter = instantiatePresenter()
}
override fun showError(error: String) {
(activity as BaseActivity<BasePresenter<BaseView>>).showError(error)
}
override fun showError(errorResId: Int) {
(activity as BaseActivity<BasePresenter<BaseView>>).showError(errorResId)
}
abstract fun onBackPressed(): Boolean
/**
* Instantiates the presenter the Fragment is based on.
*/
protected abstract fun instantiatePresenter(): P
abstract val TAG: String
Incident Fragment code:
class IncidentScreenFragment: BaseFragment<IncidentScreenPresenter>(), BaseView, IncidentView, AlertFilterListener, AlertItemClickListener, IncidentDetailListener {
var rvAdapter : IncidentAdapter? = null
var state : Int = LIST_STATE
override fun instantiatePresenter(): IncidentScreenPresenter {
return IncidentScreenPresenter(this)
}
override val TAG: String
get() = "INCIDENT"
override fun getContext(): Context {
return activity as Context
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
super.onCreateView(inflater, container, savedInstanceState)
return inflater.inflate(R.layout.fragment_incident, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews()
presenter.onViewCreated()
initObserve()
}
private fun initViews(){
//Reclycler view
alertRV.layoutManager = LinearLayoutManager(context)
rvAdapter = IncidentAdapter(ArrayList(), context, this)
alertRV.adapter = rvAdapter
//Apply Listeners
headerBox.setFilterListener(this)
incidentDetailView.setListener(this)
}
override fun initObserve() {
//Init observe presenter model
val alertObserver = Observer<ArrayList<AlertModel>> { alerts ->
Timber.d("Data received from Presenter [$alerts]")
showAlertList(alerts)
}
presenter.filteredAlertList.observe(context as BaseActivity<BasePresenter<BaseView>>,alertObserver)
}
override fun updateThisFilters(boxState: Boolean, level: Int) {
presenter.updateFilterList(boxState,level)
}
fun showOnlyThisLevel(level:Int){
presenter.showOnlyThisLevel(level)
headerBox.disableBoxExcept(level)
}
fun showAlertList(list: ArrayList<AlertModel>){
rvAdapter?.updateData(list)
}
override fun onItemClick(model: AlertModel) {
presenter.loadAlertDetail(model)
}
override fun showAlertDetail(model: AlertModel) {
incidentDetailView.setUpFromModel(model)
WhiteWizard.slideLeftEffect(incidentDetailView,incidentListRootElement)
state = DETAIL_STATE
}
override fun onbackFromDetailPressed() {
WhiteWizard.slideRightEffect(incidentListRootElement,incidentDetailView)
state = LIST_STATE
}
override fun showLoader() {
loaderIncident.visibility = View.VISIBLE
}
override fun hideLoader() {
loaderIncident.visibility = View.INVISIBLE
}
override fun onBackPressed(): Boolean {
when(state){
LIST_STATE -> return false
DETAIL_STATE -> {
onbackFromDetailPressed()
return true
}
else -> return false
}
}
fun newInstance(): IncidentScreenFragment {
return IncidentScreenFragment()
}
}
When i click on the button in homePage to display fragment content i got:
Process: XXXXXX, PID: 3192
kotlin.UninitializedPropertyAccessException: lateinit property presenter has not been initialized
at infinite_software.intelligence_center.intelligencecenter.base.BaseFragment.getPresenter(BaseFragment.kt:11)
at XXXXXX.ui.home.incidentScreen.IncidentScreenFragment.showOnlyThisLevel(IncidentScreenFragment.kt:78)
at XXXXXX.ui.home.HomeActivity.filterDataWithSeverity(HomeActivity.kt:110)
at XXXXXX.ui.home.homeScreen.HomeScreenFragment.filterBy(HomeScreenFragment.kt:76)
at XXXXXX.ui.home.homeScreen.HomeScreenFragment$initViews$5.onClick(HomeScreenFragment.kt:56)
If i try to print the id of Fragment, i obtain 2 different ids from method call showOnlyThisLevel() and onBackPressed(). What i miss ?
After doing some research, it seems that the problem stems from the misnaming of FragmentPagerAdapter's method - being named getItem(), but not clearly specifying that the abstract method getItem(int position) is supposed to return a new instance of a fragment rather than just "get an instance of one".
Of course, there is not much we can do about an incorrect name after it's been out in the wild for 7 years, but at least we can fix the bug that stems from this issue in your code ;)
Without further ado, the cause of your NPE is that onCreateView (where your Presenter is instantiated) is never called.
This happens because you are creating the fragment here:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
...
homeFragment = HomeScreenFragment.newInstance()
incidentFragment = IncidentScreenFragment.newInstance()
}
You return this fragment from inside getItem(int position) in your FragmentPagerAdapter:
override fun getItem(position: Int): Fragment = when(position) {
...
1 -> activity.incidentFragment
...
}
So what we know about activity.incidentFragment is that in it, onCreateView() is never called.
This is caused by the fact that it's never actually added to a FragmentManager and never displayed on the screen.
That's because super.onCreate(savedInstanceState) in Activity recreates all Fragments, using their no-args constructor, via reflection, while keeping their tag (see findFragmentByTag).
So as you can see in this answer, or as I can quote here:
// Do we already have this fragment?
String name = makeFragmentName(container.getId(), itemId);
Fragment fragment = mFragmentManager.findFragmentByTag(name);
if (fragment != null) {
if (DEBUG) Log.v(TAG, "Attaching item #" + itemId + ": f=" + fragment);
mCurTransaction.attach(fragment);
} else {
fragment = getItem(position);
if (DEBUG) Log.v(TAG, "Adding item #" + itemId + ": f=" + fragment);
mCurTransaction.add(container.getId(), fragment,
makeFragmentName(container.getId(), itemId));
The getItem(position) method is only called if the Fragment is not found by the fragment tag that the FragmentPagerAdapter sets for the fragment, which IS automatically recreated after low memory condition kills your app.
Therefore, YOUR new fragment (that you create by hand in the Activity) is NEVER used, and therefore it has no view, never initialized, never added to FragmentManager, it's not the same instance as what's actually inside your ViewPager, and it crashes when you call it. Boom!
Solution is to instantiate the Fragment inside FragmentPagerAdapter's getItem(position) method. To get an instance of the fragment, use this answer.
I am trying to implement a simple swiping action between the three main screens of my app (feed, forum & profile).
I've been looking for guides online but they all seem to take the same approach and show how to implement the adapter using fragments that are simple instances of a basic class that for example just inflates a title and a number on the screen.
In my case, the fragments are not all instances of the same class and are completely different.
M problem lays in the FragmentPagerAdapter. I am not sure how to return each fragment in the getItem method.
I have tried the following but it doesn't except it as a valid return statement and is still expecting one:
class PagesPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
override fun getItem(p0: Int): Fragment? {
return when (p0){
0 -> FeedFragment.newInstance()
1 -> BoardFragment.newInstance()
2 -> ProfileFragment.newInstance()
else -> FeedFragment.newInstance()
}
}
override fun getCount(): Int {
return 3
}
}
This is an example of one of my fragments:
class FeedFragment : Fragment() {
val galleryAdapter = GroupAdapter<ViewHolder>()
private fun setUpGalleryAdapter() {
feed_gallary.adapter = galleryAdapter
val galleryLayoutManager = GridLayoutManager(this.context, 3)
feed_gallary.layoutManager = galleryLayoutManager
val dummyUri =
"https://firebasestorage.googleapis.com/v0/b/dere-3d530.appspot.com/o/20150923_100950.jpg?alt=media&token=97f4b02c-75d9-4d5d-bc86-a3ffaa3a0011"
val imageUri = Uri.parse(dummyUri)
if (imageUri != null) {
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
galleryAdapter.add(FeedImage(imageUri))
}
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
setUpGalleryAdapter()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_feed, container, false)
companion object {
fun newInstance(): FeedFragment = FeedFragment()
}
}
And this is my main activity:
class MainActivity : AppCompatActivity() {
private lateinit var viewPager: ViewPager
private lateinit var pagerAdapter: PagesPagerAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager = findViewById<ViewPager>(R.id.view_pager)
pagerAdapter = PagesPagerAdapter(supportFragmentManager)
viewPager.adapter = pagerAdapter
}
}
I have also tried to define a variable currentFragment, have it reassigned for each of the when statements and have it be the return value, but as all my fragments are unique I am getting a type mismatch when trying to assign them all to one variable.
Is there some Array I can store the fragments in and then return the array position?
I feel like there is probably a simple elegant and common solution here.
Without seeing all of your code I cannot be sure but one thing to note is you must return an instance of your fragment not a reference to the class. Ex: FeedFragment() instead of FeedFragment.
Also, your when statement does not have a default case resulting in a compile error for the getItem method as you have a code path that doesn't return anything. Try the following code for getItem
override fun getItem(p0: Int): Fragment {
when(p0){
0 -> return FeedFragment.newInstance()
1 -> return BoardFragment.newInstance()
else -> return ProfileFragment.newInstance()
}
}
Unfortunately, there is no elegant solution for this case. You can store created fragment in array and then use this array when you need to access related fragment instance.
class PagesPagerAdapter(fragmentManager: FragmentManager) : FragmentPagerAdapter(fragmentManager) {
private val fragments = SparseArray<WeakReference<Fragment>>()
override fun getCount() = 3
override fun getItem(position: Int): Fragment? {
val fragmentReference = fragments[position]
return if (fragmentReference?.get() != null) {
fragmentReference.get()
} else {
return when (position) {
0 -> FeedFragment.newInstance()
1 -> BoardFragment.newInstance()
2 -> ProfileFragment.newInstance()
else -> error("Incorrect position: $position")
}
}
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val fragment = super.instantiateItem(container, position) as Fragment
fragments.put(position, WeakReference(fragment))
return fragment
}
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