I have an Activity with a ViewPager containing Fragments that are summoned with custom buttons. Currently, my custom adapter's getItem method is being called, but the pager is going blank rather than going to the new fragment
Here is my adapter :
class ScreenSlidePagerAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
private val NUM_FRAGMENTS = 2
override fun getCount(): Int = NUM_FRAGMENTS
override fun getItem(position: Int): Fragment {
var fragment: Fragment = WelcomeFragment.newInstance()
when (position) {
0 -> fragment = WelcomeFragment.newInstance()
1 -> fragment = LanguageSelectFragment.newInstance()
}
println(position)
return fragment
}
}
Here is the method that gets called on button click. The views are given a tag corresponding to their Fragment's position in the adapter, and this is retrieved on click to pull up the right fragment.
private fun switchScreens(view: View) {
val fragment = mPagerAdapter.getItem(view.getTag().toString().toInt())
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.main_view_pager, fragment)
fragmentTransaction.commit()
}
And my fragment:
class LanguageSelectFragment : Fragment() {
private var listener: OnFragmentInteractionListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
return inflater.inflate(R.layout.fragment_language_select, container, false)
}
fun onButtonPressed(uri: Uri) {
listener?.onFragmentInteraction(uri)
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnFragmentInteractionListener) {
listener = context
} else {
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
interface OnFragmentInteractionListener {
fun onFragmentInteraction(uri: Uri)
}
companion object {
#JvmStatic
fun newInstance() =
LanguageSelectFragment().apply {
arguments = Bundle().apply {
}
}
}
}
Do you have a ViewPager container for your framgents ?
If yes, that's not the way to use the pagerAdapter. You should see this link. It's in Java but you can easily adapt it to kotlin.
If no, I'm not sure what you are trying to do but I think the
ViewPager is what you need.
If you just want to switch fragments on a click, just use a FrameLayout to display the fragment you want and switch between them with transactions. I'm not familiar with kotlin but something like this should work.
private fun switchScreens(i: Integer) {
val fragment;
when (i) {
0 -> fragment = WelcomeFragment.newInstance()
1 -> fragment = LanguageSelectFragment.newInstance()
}
val fragmentManager = supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.replace(R.id.main_view_pager, fragment)
fragmentTransaction.commit()
}
If you want to slide to one fragment to another, you can use a ViewPager (see the link above).
Best
Related
I have a settings screen which has a FrameLayout that loads a fragment containing a RecyclerView. The RecyclerView has selectable items, which are handled by the onItemClick function inside the fragment.
The objective, is to replace the current opened fragment with another one.
Is it possible to replace the current fragment while in the onItemClick function? If so, how?
Code
Fragment used for handling the RecyclerView and the onItemClick function
class FragmentSettingsMain : Fragment(), AdapterSettings.OnItemClickListener {
lateinit var settingsList: List<DataItemsSettings>
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
retainInstance = true
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 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, this#FragmentSettingsMain)
}
}
override fun OnItemClick(position: Int) {
when(position) {
0 -> //NEED TO REPLACE FRAGMENT HERE
1 -> //NEED TO REPLACE FRAGMENT HERE
2 -> //NEED TO REPLACE FRAGMENT HERE
3 -> //NEED TO REPLACE FRAGMENT HERE
4 -> //NEED TO REPLACE FRAGMENT HERE
5 -> this.startActivity(Intent(context, ActivityAbout::class.java))
}
}
}
Activity where the FrameLayout is
class ActivitySettings : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_settings)
topToolbarBack.setNavigationOnClickListener {
finish()
}
val frgSettingsMain = FragmentSettingsMain()
setCurrentFragment(frgSettingsMain)
}
private fun setCurrentFragment(fragment: Fragment) =
supportFragmentManager.beginTransaction().apply {
replace(R.id.framelayoutSettings, fragment)
commit()
}
}
You can try this
override fun OnItemClick(position: Int) {
when(position) {
0 -> addContentFragment(yourFragment(),true)
1 -> //NEED TO REPLACE FRAGMENT HERE
2 -> //NEED TO REPLACE FRAGMENT HERE
3 -> //NEED TO REPLACE FRAGMENT HERE
4 -> //NEED TO REPLACE FRAGMENT HERE
5 -> this.startActivity(Intent(context, ActivityAbout::class.java))
}
}
fun addContentFragment(fragment: androidx.fragment.app.Fragment?, addToBackStack: Boolean) {
activity?.let {it->
if (!it.isFinishing) {
if (fragment == null) {
return
}
it.supportFragmentManager.let {it1->
val fragmentManager =it.supportFragmentManager
val currentFragment =
fragmentManager.findFragmentById(R.id.framelayoutSettings)
if (currentFragment != null && fragment.javaClass.isAssignableFrom(
currentFragment.javaClass
)
) {
return
}
val fragmentTransaction = fragmentManager.beginTransaction()
fragmentTransaction.add(
R.id.framelayoutSettings,
fragment,
fragment.javaClass.name
)
if (addToBackStack) {
fragmentTransaction.addToBackStack(fragment.javaClass.name)
}
fragmentTransaction.commit()
}
}
}
}
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);
}
I have a fragment class that contains a ViewPager with two sites. These sites contain a few widgets like CheckBoxes and I want them to stay checked when the orientation changes.
MainFragment:
class StatsFragment: Fragment() {
private val fragmentStatsCat = StatsCategoryFragment()
private val fragmentStatsMon = StatsMonthFragment()
private lateinit var pager: ViewPager
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view: View = inflater.inflate(R.layout.fragment_stats, container, false)
pager = view.findViewById(R.id.stats_container)
val pagerAdapter = StatsScreenSlidePagerAdapter(childFragmentManager)
pager.adapter = pagerAdapter
return view
}
private inner class StatsScreenSlidePagerAdapter(fm: FragmentManager): FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
return if (position == 0) {
fragmentStatsCat
} else {
fragmentStatsMon
}
}
override fun getCount(): Int = 2
override fun saveState(): Parcelable? {
return null
}
}
}
One of the ViewPagerFragments:
class StatsMonthFragment: Fragment() {
companion object {
private const val CHECKBOX_KEY = "isChecked"
private const val CATEGORY_KEY = "category"
}
private lateinit var btnPrevCat: ImageButton
private lateinit var btnNextCat: ImageButton
private lateinit var cbAllCats: CheckBox
private lateinit var categories: List<Category>
private var i: Int = 0
private var isCbChecked: Boolean? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState != null) {
i = savedInstanceState.getInt(CATEGORY_KEY)
isCbChecked = savedInstanceState.getBoolean(CHECKBOX_KEY)
}
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_stats_months, container, false)
btnNextCat = view.findViewById(R.id.btn_next_cat)
btnPrevCat = view.findViewById(R.id.btn_prev_cat)
cbAllCats = view.findViewById(R.id.cb_all_cats)
categories = DatabaseInitializer.getInstance().getAllCategories(AppDatabase.getInstance(context).categoryDao())
if(isCbChecked == null || isCbChecked!!) {
val initFragment = StatsSelectedFilterFragment() //This is just a fragment with a TextView; I use fragments for that for the CustomAnimations
initFragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction().add(R.id.container_stats_categories, initFragment).commit()
} else {
val fragment = StatsSelectedFilterFragment()
fragment.setText(getString(R.string.all_categories))
childFragmentManager.beginTransaction()
.replace(R.id.container_stats_categories, fragment)
.commit()
}
btnNextCat.setOnClickListener {
val fragment = StatsSelectedFilterFragment()
fragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
.replace(R.id.container_stats_categories, fragment)
.commit()
}
btnPrevCat.setOnClickListener {
val fragment = StatsSelectedFilterFragment()
fragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction()
.setCustomAnimations(R.anim.slide_in_left, R.anim.slide_out_right)
.replace(R.id.container_stats_categories, fragment)
.commit()
}
cbAllCats.setOnCheckedChangeListener{_,checked->
if(checked) {
val fragment = StatsSelectedFilterFragment()
fragment.setText(getString(R.string.all_categories))
childFragmentManager.beginTransaction()
.replace(R.id.container_stats_categories, fragment)
.commit()
} else {
val fragment = StatsSelectedFilterFragment()
fragment.setText(categories[i].cat_name)
childFragmentManager.beginTransaction()
.replace(R.id.container_stats_categories, fragment)
.commit()
}
}
return view
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
val isAllCatsChecked = cbAllCats.isChecked
outState.putBoolean(CHECKBOX_KEY, isAllCatsChecked)
outState.putInt(CATEGORY_KEY, i)
}
}
Now on every screen rotation, the fragment gets recreated twice, first with savedInstanceState != null and then with savedInstanceState == null, which means that I don't have access to the old settings in the Fragments. As I know, it is because the fragment gets recreated, and then the MainFragment containing the viewpager gets also recreated, which means that the fragments within the viewPager get created a second time. I have tried something like this in the MainFragment:
private lateinit var fragmentStatsCat: StatsCategoryFragment
private lateinit var fragmentStatsMon: StatsMonthFragment
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState == null) {
fragmentStatsMon = StatsMonthFragment()
fragmentStatsCat = StatsCategoryFragment()
} else {
//Finding old fragments
}
}
Maybe this would solve the problem (I am not sure), but I don't know how I can find the fragments, as they are inside a ViewPager.
EDIT
I have now found a solution to find the fragments when the activity is recreated:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if(savedInstanceState == null)
{
fragmentStatsMon = StatsMonthFragment()
fragmentStatsCat = StatsCategoryFragment()
} else {
fragmentStatsMon = childFragmentManager.getFragment(savedInstanceState, FRAG_MONTH_TAG) as StatsMonthFragment
fragmentStatsCat = childFragmentManager.getFragment(savedInstanceState, FRAG_CAT_TAG) as StatsCategoryFragment
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
childFragmentManager.putFragment(outState, FRAG_CAT_TAG, fragmentStatsCat)
childFragmentManager.putFragment(outState, FRAG_MONTH_TAG, fragmentStatsMon)
}
This puts a reference in the Bundle with which I can get the fragment afterwards. But now, I have another problem. When the activity is recreated, it throws the following exception:
java.lang.IllegalStateException: Fragment already added: StatsCategoryFragment{f06ec65 (a1991f39-3af4-4a74-9aeb-79274beae04a) id=0x7f09014a}
I don't know how to solve that because I don't really manually add the fragments. Also, I don't really get it because the viewpager and the adapter also get recreated, don't they? So why are the fragments still attached to the adapter?
I also don't know in which line of code this happens, I could neither find it out in the logcat nor by debugging.
Put this line in AndroidManifest.xml in the entry of your Activity in which you are loading your Fragments:
android:configChanges="layoutDirection|keyboardHidden|orientation|screenSize"
By this, Data will not be reset when your Fragment is recreated by any condition.
Example:
<activity
android:name=".activity.MainActivity"
android:configChanges="layoutDirection|keyboardHidden|orientation|screenSize"
android:label="#string/app_name"></activity>
Check out this similar question
https://www.google.com/url?sa=t&source=web&rct=j&url=https://stackoverflow.com/questions/15313598/once-for-all-how-to-correctly-save-instance-state-of-fragments-in-back-stack&ved=2ahUKEwja6rasobzjAhVh1uAKHRakDXIQFjAAegQIBhAB&usg=AOvVaw0Hg_vwSnBayxV1EJfKOUZk
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 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