The following problem occurred with me after migration from viewPager to viewPager2 - when I open my app, the tablyout indicator is missing by default. But if you make some swiping, the indicator will appears! Looks weird, because I made everything correct. There are my simplified Kotlin-code:
class ScheduleWeekFragment : ScheduleBaseFragment<ScheduleWeekViewModel>(ScheduleWeekViewModel::class) {
private val viewBinding by viewBinding(FragmentWeekBinding::bind)
private var titles: Array<String>? = null
// listeners
private val viewPagerListener = object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
viewModel.updateDayTitle(position)
}
}
// observers
private val selectedDayObserver = Observer<Int> {
viewBinding.viewPager2.currentItem = it
}
// lifecycle
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
titles = context?.resources?.getStringArray(R.array.schedule_fragment_day_abbreviations)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? =
inflater.inflate(R.layout.fragment_week, container, false)
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewBinding.viewPager2.adapter = ScheduleViewPagerAdapter(this)
viewBinding.viewPager2.setPageTransformer(ZoomOutPageTransformer)
TabLayoutMediator(viewBinding.tabLayout, viewBinding.viewPager2) { tab, position ->
tab.text = titles?.getOrNull(position)
}.attach()
// observers
viewModel.viewPagerPosition.observe(viewLifecycleOwner, selectedDayObserver)
// listeners
viewBinding.viewPager2.registerOnPageChangeCallback(viewPagerListener)
}
override fun onDestroyView() {
viewBinding.viewPager2.unregisterOnPageChangeCallback(viewPagerListener)
super.onDestroyView()
}
}
And there is my simplified xml-code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/fragmentWeek"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/colorPrimary"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.google.android.material.tabs.TabLayout
android:id="#+id/tabLayout"
style="#style/TabLayout1"
android:layout_width="match_parent"
android:layout_height="40dp"
android:verticalScrollbarPosition="left"
app:layout_constraintEnd_toEndOf="#+id/viewPager2"
app:layout_constraintStart_toStartOf="#+id/viewPager2"
app:layout_constraintTop_toBottomOf="#+id/scheduleToolbar"
app:tabGravity="start"
app:tabMode="scrollable"
android:background="#color/white"
tools:ignore="SpeakableTextPresentCheck" />
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/viewPager2"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_alignParentStart="true"
android:layout_alignParentTop="true"
android:background="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tabLayout"
tools:ignore="SpeakableTextPresentCheck" />
</androidx.constraintlayout.widget.ConstraintLayout>
The problem was inside my observer:
private val selectedDayObserver = Observer<Int> {
// viewBinding.viewPager2.currentItem = it // wrong!
viewBinding.viewPager2.setCurrentItem(it, false) // correct!
}
I didn't find why in the official docs, but difference exists: the first variant works good with viewpager1, but for viewpager2, be careful and use the second complex variant.
Related
I'm trying to implement viewpager2 in a fragment which shows a list of notes with Recycler View. I tried different solutions but I'm unable to figure it out.
Here is my fragment_notes_list.xml :
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/notes_recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
list_item_notes.xml used to display recycler view viewholder:
<androidx.cardview.widget.CardView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:card_view="http://schemas.android.com/apk/res-auto"
android:id="#+id/card_pertanyaan"
android:layout_width="match_parent"
android:layout_height="96dp"
android:layout_marginLeft="16dp"
android:layout_marginTop="12dp"
android:layout_marginRight="16dp"
android:clickable="true"
android:foreground="?selectableItemBackground"
card_view:cardBackgroundColor="#595959"
card_view:cardCornerRadius="8dp"
card_view:contentPadding="10dp">
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/linearLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/notes_title"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
android:text="Title"
android:textColor="#color/white"
app:layout_constraintBottom_toTopOf="#+id/notes_body"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_chainStyle="packed" />
<TextView
android:id="#+id/notes_body"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:textColor="#color/white"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/notes_title" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
Relevant snippets from fragment class NotesListFragment.kt :
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_notes_list, container, false)
notesRecyclerView =
view.findViewById(R.id.notes_recycler_view) as RecyclerView
registerForContextMenu(notesRecyclerView)
notesRecyclerView.layoutManager = LinearLayoutManager(context)
notesRecyclerView.adapter = adapter
return view
}
private inner class ScreenSlidePagerAdapter(fa: Fragment) : FragmentStateAdapter(fa) {
override fun getItemCount(): Int = NUM_PAGES
override fun createFragment(position: Int): Fragment {
return if (position == 0) {
NotesListFragment()
} else {
CalenderFragment()
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
notesListViewModel.notesListLiveData.observe(
viewLifecycleOwner,
Observer { notes ->
notes?.let {
Log.i(TAG, "got notes")
updateUI(notes)
}
})
}
private inner class NotesAdapter(var notes: List<Notes>) : RecyclerView.Adapter<NotesHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotesHolder {
val view = layoutInflater.inflate(R.layout.list_item_notes, parent, false)
/* viewPager = view.findViewById(R.id.pager)
val pagerAdapter = ScreenSlidePagerAdapter(this#NotesListFragment)
viewPager.adapter = pagerAdapter */
return NotesHolder(view)
}
override fun getItemCount() = notes.size
override fun onBindViewHolder(holder: NotesHolder, position: Int) {
val note = notes[position]
holder.bind(note)
}
}
I'm confused should I add ViewPager2 with FrameLayout in list_item_notes.xml?
But then how can I implement ScreenSliderPagerAdapter for it?
I assume you would like to have 2 fragments: list and calendar and thats why you need to implement ViewPager2. Your NotesListFragment looks good. You have recyclerview with notes there, but what you want now is to provide list of NotesListFragment and CalendarFragment which will be scrollable in left/right direction. First, extract ScreenSlidePagerAdapter outside of your fragment code. Next you have to add ViewPager2 to your parent activity/fragment
<androidx.viewpager2.widget.ViewPager2
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
And then in your parent activity/fragment pair it with adapter like this way:
val viewPager = findViewById(R.id.pager)
val pagerAdapter = ScreenSlidePagerAdapter(this)
viewPager.adapter = pagerAdapter
You can read more about this in official docs: https://developer.android.com/training/animation/screen-slide-2
I had tried implementing ViewPager in Main Activity earlier also. But this way i cannot close viewpager fragments since they are always visible. How do I get around this?
here is my main activity after the changes you suggested:
class MainActivity : AppCompatActivity(),
NotesListFragment.Callbacks {
private lateinit var viewPager: ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewPager = findViewById(R.id.pager)
val pagerAdapter = ScreenSlidePagerAdapter(this)
viewPager.adapter = pagerAdapter
}
override fun onNotesSelected(notesId: UUID) {
val fragment = NotesFragment.newInstance(notesId)
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.addToBackStack(null)
.commit()
}
fun onNotesDeleted(notes: Notes) {
val fragment = NotesListFragment()
supportFragmentManager
.beginTransaction()
.replace(R.id.fragment_container, fragment)
.commit()
Toast.makeText(this, "Note Deleted", Toast.LENGTH_SHORT).show()
}
mainactivity.xml:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/fragment_container"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.viewpager2.widget.ViewPager2
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/pager"/>
</FrameLayout>
here are some screenshots,
https://drive.google.com/drive/folders/13ITZqcaeDeydCe0tW2Iu7VoSGSZnrlkg?usp=sharing
Solved the problem.
Since, I was unable to add Viewpager with recycler view of NotesListFragment, I made another Parent Fragment with viewpager and put NotesListFragment inside it.
I have the Figma model, but can't do right, in all cases that I've tried, I've got too wide tab indicator or too compressed. I think, maybe exist some solution to add extra space between tabs. I can`t add the image to this post, i put link on my google photo. You will see 2 photo, left photo - What i need
https://photos.app.goo.gl/CiwR7DemvCcQxWFz6
XML for
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".activity.QuizActivity">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
android:layout_width="match_parent"
android:layout_height="90dp"
android:background="#color/colorBolotoButton"
app:layout_scrollFlags="scroll|enterAlways"
app:popupTheme="#style/ThemeOverlay.AppCompat.Light">
<TextView
android:id="#+id/textViewToolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_alignParentRight="true"
android:layout_marginRight="120dp"
android:fontFamily="#font/bellota_bold"
android:gravity="center_vertical"
android:text="Проверим знания!"
android:textColor="#color/cardview_light_background"
android:textSize="24dp" />
</androidx.appcompat.widget.Toolbar>
<com.google.android.material.tabs.TabLayout
android:layout_marginTop="16dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_width="wrap_content"
android:id="#+id/tabLayout"
app:tabGravity="center"
android:layout_gravity="center_horizontal"
app:tabIndicatorGravity="center"
app:tabMode="fixed"
app:tabIndicator="#drawable/circle"
app:tabIndicatorColor="#color/colorBolotoButton"
app:tabIndicatorHeight="32dp"
android:clipToPadding="true"
app:tabSelectedTextColor="#color/colorWhite"
android:clickable="false" >
</com.google.android.material.tabs.TabLayout>
<androidx.viewpager2.widget.ViewPager2
android:id="#+id/quizViewPager"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="10dp" />
</LinearLayout>
Adapter
class QuizAdapter(activity: AppCompatActivity, val itemsCount: Int)
: FragmentStateAdapter(activity) {
override fun getItemCount(): Int {
return itemsCount
}
override fun createFragment(position: Int): Fragment {
return QuizFragment.getinstance(position)
}
}
Activity
class QuizActivity : AppCompatActivity() {
private var testArray = arrayListOf("1", "2", "3", "4", "5", "6", "7", "8")
// private var QuizPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {
// override fun onPageSelected(position: Int) {
// Toast.makeText(this#QuizActivity, "Selected position: ${position}",
// Toast.LENGTH_SHORT).show()
// }
// }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_test)
val viewPager: ViewPager2 = findViewById(R.id.quizViewPager)
val pagerAdapter = QuizAdapter(this, testArray.size)
viewPager.adapter = pagerAdapter
/*
Method for concatenation ViewPager and tabLayout
*/
TabLayoutMediator(tabLayout, quizViewPager) { tab, position ->
tab.text=testArray[position]
}.attach()
Fragment
class QuizFragment : Fragment() {
companion object {
const val ARG_PAGE = "ARG_PAGE"
// Method for creating new instances of the fragment
fun getinstance(page: Int): QuizFragment {
val fragment = QuizFragment()
val args = Bundle()
args.putInt(ARG_PAGE, page)
fragment.arguments = args
return fragment
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val position = requireArguments().getInt(ARG_PAGE) + 1
textViewFragment.text = "Fragment #$position"
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val viewfragment = inflater.inflate(R.layout.fragment_page, container, false)
var position = requireArguments().getInt(ARG_PAGE)
val textView = viewfragment.findViewById<TextView>(R.id.textViewFragment)
textView.setText("Fragment #${++position}")
return viewfragment
}
}
My application has a ViewPager with a bunch of fragments, when a button is clicked in Fragment1, actions are performed and a TransitionFragment appears, when a button is clicked in a TransitionFragment, the ViewPager transitions to Fragment3, this is how I implemented it:
MainActivity.class
class MainActivity : AppCompatActivity() {
private var mAdView: AdView? = null
#SuppressLint("ResourceType")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adRequest = AdRequest.Builder().build()
mAdView = findViewById(R.id.adView)
mAdView!!.loadAd(adRequest)
initTab()
}
fun initTab() {
tab_layout.addTab(tab_layout.newTab().setIcon(R.drawable.image1)
tab_layout.addTab(tab_layout.newTab().setIcon(R.drawable.image2)
tab_layout.addTab(tab_layout.newTab().setIcon(R.drawable.image3)
tab_layout.tabGravity = TabLayout.GRAVITY_FILL
val adapter = MyPagerAdapter(supportFragmentManager, tab_layout.tabCount)
pager.adapter = adapter
pager.offscreenPageLimit = 2
pager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tab_layout))
tab_layout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
pager.currentItem = tab.position
if (tab.position == 0) {
tab_layout.getTabAt(0)!!.setIcon(R.drawable.image1_focus)
if (getSupportFragmentManager().findFragmentById(R.id.frame) != null) {
getSupportFragmentManager().popBackStack();
}
}
if (tab.position == 1) {
tab_layout.getTabAt(1)!!.setIcon(R.drawable.image2_focus)
if (getSupportFragmentManager().findFragmentById(R.id.frame) != null) {
getSupportFragmentManager().popBackStack();
}
}
if (tab.position == 2) {
tab_layout.getTabAt(2)!!.setIcon(R.drawable.image3_focus)
if (getSupportFragmentManager().findFragmentById(R.id.frame) != null) {
getSupportFragmentManager().popBackStack();
}
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {
if (tab.position == 0) tab_layout.getTabAt(0)!!.setIcon(R.drawable.image1)
if (tab.position == 1) tab_layout.getTabAt(1)!!.setIcon(R.drawable.image2)
if (tab.position == 2) tab_layout.getTabAt(2)!!.setIcon(R.drawable.image3)
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".MainActivity">
<FrameLayout
android:id="#+id/frame"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#id/tab_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.google.android.gms.ads.AdView
android:id="#+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#color/transparent"
app:adSize="SMART_BANNER"
app:adUnitId="#string/banner_key"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
style="#style/MyToolbar"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginTop="2dp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/adView"
app:popupTheme="#style/AppTheme.PopupOverlay">
<TextView
android:id="#+id/toolbar_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="#font/sf_pro_display_regular"
android:textColor="#color/black"
android:textSize="24sp"
tools:layout_editor_absoluteX="206dp"
tools:layout_editor_absoluteY="7dp" />
</androidx.appcompat.widget.Toolbar>
<androidx.viewpager.widget.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toTopOf="#+id/tab_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar" />
</LinearLayout>
</FrameLayout>
<com.google.android.material.tabs.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#fff"
android:elevation="6dp"
android:minHeight="?attr/actionBarSize"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tabBackground="#drawable/tab_color_selector"
app:tabIndicatorColor="#2196F3"
app:tabIndicatorGravity="top"
app:tabIndicatorHeight="4dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
Fragment1.class
class Fragment1 : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment1, container, false)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
optbutton.setOnClickListener {
val transitionFragment: TransitionFragment = TransitionFragment().newInstance(2)!!
val trans = fragmentManager!!.beginTransaction()
trans.replace(R.id.frame, transitionFragment)
trans.addToBackStack(null)
trans.commit()
}
}
}
Fragment1 and TransitionFragment only have a button
TransitionFragment.class
class TransitionFragment() : Fragment() {
private var index: Int = 0
fun newInstance(index: Int): TransitionFragment? {
val fragment = TransitionFragment()
val args = Bundle()
args.putInt("index", index)
fragment.setArguments(args)
return fragment
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.transition_fragment, container, false)
val trans_btn: Button = view.findViewById(R.id.trans_btn)
if (getArguments() != null) {
index = getArguments()!!.getInt("index")
}
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
trans_btn.setOnClickListener {
val tabs: TabLayout = activity!!.findViewById(R.id.tab_layout)
tabs.getTabAt(index)!!.select()
}
}
}
Everything works well, but in rare cases the metric receives a crash notification like this:
java.lang.IllegalStateException: Fragment e{2dc5257 (30032337-24f8-4d9a-9da9-9db92da88082)} not attached to a context.
at androidx.fragment.app.d.q(Unknown Source)
at androidx.fragment.app.d.u(Unknown Source)
at androidx.fragment.app.d.a(Unknown Source)
at {packageWithFragment}.e.aE(Unknown Source)
at {packageWithFragment}.e.c(Unknown Source)
at {packageWithFragment}.e$f.b(Unknown Source)
at com.hookedonplay.decoviewlib.b.a.n(Unknown Source)
at com.hookedonplay.decoviewlib.a.b$2.a(Unknown Source)
at com.a.a.j.n(Unknown Source)
at com.a.a.j.c(Unknown Source)
at com.a.a.j$a.handleMessage(Unknown Source)
at android.os.Handler.dispatchMessage(Handler.java:111)
at android.os.Looper.loop(Looper.java:227)
at android.app.ActivityThread.main(ActivityThread.java:6102)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:961)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:822)
I can't figure out where the error is and what is causing the crash. Please tell me what could be wrong and how to fix it correctly?!
I had kind of similiar case: when I went back from a fragment, I got crashed and had the same error as yours.
I did a simple check and put Log.i ("info", x) after every few lines in the fragment code and also overrdie onStop and put Log inside it (increasing the x each time). Surprisingly I found out that the fragment code keeps running after onStop, and got crashed whan it comes to get a view like a Textview etc, because the Fragment Context is NULL now... (In my case it was because I have a calling to firebase database, pulling data and then write it to a view. It takes a while, and if I leave the fragment before this proccess ends, the view in null).
I've searched a lot but couldn't find any information or other questions about it... weird.
My soulotion: I made a boolean flag, initialize it with true at onCreateView and assign false to it at onStop. Now, before I call any view at the code, I validate the flag isn't false. That solved my crashes.
I still will be glad to understand that life-cycle and why Fragment code keeps runing after onStop... I hope I helped you.
I found an interesting feature in one of the applications, I want to repeat it. The application has ViewPager and TabLayout and a bunch of fragments inside.
On the first fragment there is a button img1
, by pressing it, the fragment is replaced by another img2
while ads_banner is removed and the toolbar is replaced,
and when swiping to the right and back, it shows fragment1, please tell me how this can be done. Here is what I already wrote:
MainActivity
class MainActivity : AppCompatActivity() {
private var mAdView: AdView? = null
#SuppressLint("ResourceType")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val adRequest = AdRequest.Builder().build()
mAdView = findViewById(R.id.adView)
mAdView!!.loadAd(adRequest)
initTab()
}
fun initTab() {
tab_layout.addTab(tab_layout.newTab().setIcon(R.drawable.image1)
tab_layout.addTab(tab_layout.newTab().setIcon(R.drawable.image2)
tab_layout.addTab(tab_layout.newTab().setIcon(R.drawable.image3)
tab_layout.tabGravity = TabLayout.GRAVITY_FILL
val adapter = MyPagerAdapter(supportFragmentManager, tab_layout.tabCount)
pager.adapter = adapter
pager.offscreenPageLimit = 2
pager.addOnPageChangeListener(TabLayout.TabLayoutOnPageChangeListener(tab_layout))
tab_layout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
pager.currentItem = tab.position
if (tab.position == 0) {
tab_layout.getTabAt(0)!!.setIcon(R.drawable.image1_focus)
}
if (tab.position == 1) {
tab_layout.getTabAt(1)!!.setIcon(R.drawable.image2_focus)
}
if (tab.position == 2) {
tab_layout.getTabAt(2)!!.setIcon(R.drawable.image3_focus)
}
}
override fun onTabUnselected(tab: TabLayout.Tab) {
if (tab.position == 0) tab_layout.getTabAt(0)!!.setIcon(R.drawable.image1)
if (tab.position == 1) tab_layout.getTabAt(1)!!.setIcon(R.drawable.image2)
if (tab.position == 2) tab_layout.getTabAt(2)!!.setIcon(R.drawable.image3)
}
override fun onTabReselected(tab: TabLayout.Tab) {
}
})
}
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/mainlayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/white"
android:fitsSystemWindows="true"
android:orientation="vertical"
tools:context=".MainActivity">
<com.google.android.gms.ads.AdView
android:id="#+id/adView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="#color/transparent"
app:adSize="SMART_BANNER"
app:adUnitId="#string/banner_key"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbar"
style="#style/MyToolbar"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginTop="2dp"
android:gravity="center"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/adView"
app:popupTheme="#style/AppTheme.PopupOverlay">
<TextView
android:id="#+id/toolbar_txt"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:fontFamily="#font/sf_pro_display_regular"
android:textColor="#color/black"
android:textSize="24sp"
tools:layout_editor_absoluteX="206dp"
tools:layout_editor_absoluteY="7dp" />
</androidx.appcompat.widget.Toolbar>
<androidx.viewpager.widget.ViewPager
android:id="#+id/pager"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="#+id/tab_layout"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/toolbar" />
<com.google.android.material.tabs.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="#fff"
android:elevation="6dp"
android:minHeight="?attr/actionBarSize"
android:theme="#style/ThemeOverlay.AppCompat.Dark.ActionBar"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:tabBackground="#drawable/tab_color_selector"
app:tabIndicatorColor="#2196F3"
app:tabIndicatorGravity="top"
app:tabIndicatorHeight="4dp" />
</androidx.constraintlayout.widget.ConstraintLayout>
MyPagerAdapter
class MyPagerAdapter(fm: FragmentManager, internal var mNumOfTabs: Int) : FragmentStatePagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
when (position) {
0 -> {
return RootFragment()
// return Fragment1()
}
1 -> {
return Fragment2()
}
2 -> {
return Fragment3()
}
}
}
override fun getCount(): Int {
return mNumOfTabs
}
}
Fragment1
class Fragment1: Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_1, container, false)
return view
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
btnfr1.setOnClickListener {
val trans = fragmentManager!!.beginTransaction()
trans.replace(R.id.root_frame, Fragment4())
trans.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
trans.addToBackStack(null)
trans.commit()
}
}
RootFragment
class RootFragment : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.root_fragment, container, false)
val transaction = fragmentManager!!.beginTransaction()
transaction.replace(R.id.root_frame, Fragment1())
transaction.commit()
return view
}
}
root_fragment.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/root_frame" >
</FrameLayout>
In Fragment1, only the button for calling Fragment4. Tried to do as here https://www.codexpedia.com/android/android-viewpager-fragment-swap/
it turned out to replace Fragment1 with Fragment4, but I don’t know how to reset Fragment4 when swiping and don’t understand how to replace Toolbar and remove ads.
The trick is that as RootFragment is the fragment on the 0th index, what you need to do is show the original fragment as a child fragment of RootFragment using fragment transactions via childFragmentManager.
This way, you can replace the children of RootFragment, without actually having to replace RootFragment itself inside the ViewPager.
You need to implement createFragment and getItemCount methods in MyPagerAdapter to supply instances of fragments as pages to ViewPager.
Modify MyPagerAdapter class as shown below and test:
class MyPagerAdapter(fm: FragmentManager, internal var mNumOfTabs: Int) : FragmentStatePagerAdapter(fm) {
override fun getItemCount(): Int = mNumOfTabs
override fun createFragment(position: Int): Fragment {
when (position) {
0 -> {
return RootFragment()
}
1 -> {
return Fragment2()
}
2 -> {
return Fragment3()
}
}
}
}
I want to achieve a layout as shown in the Android Studio preview (left). However if executed in the emulator, only the button is visible and the RecyclerView is not visible/populated (right).
The XML code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
style="#style/AppTheme">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:name="com.example.app.ItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ItemFragment"
tools:listitem="#layout/fragment_item"
android:scrollbars="vertical"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="#dimen/fab_margin"
android:clickable="true"
android:focusable="true"
android:src="#android:drawable/ic_input_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
However if the RecyclerView is alone in the fragment the list is populated (but of course the action button is not showing). Code see below. And yes, my list which should be shown is not empty.
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="#+id/list"
android:name="com.example.app.ItemFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
app:layoutManager="LinearLayoutManager"
tools:context=".ItemFragment"
tools:listitem="#layout/fragment_item" >
</androidx.recyclerview.widget.RecyclerView>
I have already tried using RelativeLayout and FrameLayout, but I still get the same result. The same behavior occurs if I use e.g. a TextView instead of the action button.
--- Requested additional info ---
Adapter class (automatically generated by Android Studio, template):
class MyItemRecyclerViewAdapter(
private val mValues: List<DummyItem>,
private val mListener: OnListFragmentInteractionListener?
) : RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
private val mOnClickListener: View.OnClickListener
init {
mOnClickListener = View.OnClickListener { v ->
val item = v.tag as DummyItem
// Notify the active callbacks interface (the activity, if the fragment is attached to
// one) that an item has been selected.
mListener?.onListFragmentInteraction(item)
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.fragment_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = mValues[position]
holder.mIdView.text = item.id
holder.mContentView.text = item.content
with(holder.mView) {
tag = item
setOnClickListener(mOnClickListener)
}
}
override fun getItemCount(): Int = mValues.size
inner class ViewHolder(val mView: View) : RecyclerView.ViewHolder(mView) {
val mIdView: TextView = mView.item_number
val mContentView: TextView = mView.content
override fun toString(): String {
return super.toString() + " '" + mContentView.text + "'"
}
}
}
List fragment (automatically generated by Android Studio, template):
class ItemFragment : Fragment() {
// TODO: Customize parameters
private var columnCount = 1
private var listener: OnListFragmentInteractionListener? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
arguments?.let {
columnCount = it.getInt(ARG_COLUMN_COUNT)
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_item_list, container, false)
// Set the adapter
if (view is RecyclerView) {
with(view) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(context)
else -> GridLayoutManager(context, columnCount)
}
adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS, listener)
}
}
return view
}
override fun onAttach(context: Context) {
super.onAttach(context)
if (context is OnListFragmentInteractionListener) {
listener = context
} else {
throw RuntimeException(context.toString() + " must implement OnListFragmentInteractionListener")
}
}
override fun onDetach() {
super.onDetach()
listener = null
}
/**
* This interface must be implemented by activities that contain this
* fragment to allow an interaction in this fragment to be communicated
* to the activity and potentially other fragments contained in that
* activity.
*
*
* See the Android Training lesson
* [Communicating with Other Fragments](http://developer.android.com/training/basics/fragments/communicating.html)
* for more information.
*/
interface OnListFragmentInteractionListener {
// TODO: Update argument type and name
fun onListFragmentInteraction(item: DummyItem?)
}
companion object {
// TODO: Customize parameter argument names
const val ARG_COLUMN_COUNT = "column-count"
// TODO: Customize parameter initialization
#JvmStatic
fun newInstance(columnCount: Int) =
ItemFragment().apply {
arguments = Bundle().apply {
putInt(ARG_COLUMN_COUNT, columnCount)
}
}
}
}
Try this for your layout
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
style="#style/AppTheme"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/list"
android:name="com.example.app.ItemFragment"
android:layout_width="0dp"
android:layout_height="0dp"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:context=".ItemFragment"
tools:listitem="#layout/fragment_item" />
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/floatingActionButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|center_horizontal"
android:layout_margin="#dimen/fab_margin"
android:clickable="true"
android:focusable="true"
android:src="#android:drawable/ic_input_add"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Inside ItemFragment.kt replace onCreateView with
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_item_list, container, false)
}
After that go ahead and implement onViewCreated as such
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
with(list) {
layoutManager = when {
columnCount <= 1 -> LinearLayoutManager(requireContext())
else -> GridLayoutManager(requireContext(), columnCount)
}
adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS)
}
}
This should fix your problem because the AS template is assuming that the rootview will be a RecyclerView and is treating the whole layout as such.