In my fragment, I am displaying a list of items using a Custom View that contains a SwipeRefreshLayout.
When I swipe to refresh, I can see two refreshing indicators and one of them never disappears.
See it here
I obviously have only one SwipeRefreshLayout in my hierarchy and this appeared when I migrated my code to View Binding.
Here are the details of my code and of the behaviour.
I have a fragment that displays a list of items, I'm using a Custom View com.myapp.ItemsListView I built for this purpose.
<?xml version="1.0" encoding="utf-8"?>
<com.myapp.ItemsListView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/myItems"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
In order to access this custom view, I am using View Binding. In my fragment, that's what it looks like:
// ItemsFragment.kt
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentItemsBinding.inflate(inflater, container, false)
return binding?.root
}
and I then access anything in this custom view via binding?.myItems.
And my custom view uses a SwipeRefreshLayout as its root and contains a RecyclerView:
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/swipeToRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/items"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical" />
<TextView
android:id="#+id/missingContent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:text="#string/missing_content_message"
app:drawableTopCompat="#drawable/ic_missing_content_24dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
Following this question I'm using this piece of code to inflate the custom view:
// ItemsListView.kt
class ItemsListView(context: Context, attrs: AttributeSet) : SwipeRefreshLayout(context, attrs) {
private var b: ItemsListBinding? = null
init {
b = ItemsListBinding.inflate(LayoutInflater.from(context), this, true)
}
}
From the code, I customized my SwipeRefreshLayout to look different than the default:
b?.swipeToRefresh?.setSize(LARGE)
b?.swipeToRefresh?.setColorSchemeResources(
R.color.colorPrimary,
R.color.colorPrimaryDark
)
And I access b?.swipeToRefresh to set a listener, display or hide the progress indicator.
fun setListener(listener: OnRefreshListener) {
b?.swipeToRefresh?.setOnRefreshListener(listener)
}
fun showProgress() {
b?.swipeToRefresh?.isRefreshing = true
}
fun hideProgress() {
b?.swipeToRefresh?.isRefreshing = false
}
However, as you can see on the video clip, there is a black progress indicator that sticks and never goes away.
I have tried to access the SwipeRefreshLayout via b?.root and it results in the same behaviour.
In the end, I just do not understand why there are two SwipeRefreshLayout displayed on my screen. I noticed this problem when I introduced View Binding (I previously used Kotlin Synthetics). I'd like to inflate only one SwipeRefreshLayout in the hierarchy.
Related
I'm making app with custom dialog fragment. I created a separate xml file test_parameters.xml for this dialog fragment, but when I try to refer to views there by their id, Android Studio doesn't see them. Only views from activity_main are available. What's the reason and how can I fix it?
MainActivity.kt:
package com.example.meltflowratecalculator
import...
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
lateinit var methodsSpinner: Spinner
lateinit var timeSpinner: Spinner
lateinit var result: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
binding.settingsButton.setOnClickListener {
var dialog = CustomDialogFragment()
dialog.show(supportFragmentManager, "customDialog")
}
}
}
CustomDialogFragment.kt:
package com.example.meltflowratecalculator
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.DialogFragment
class CustomDialogFragment : DialogFragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
var rootView: View = inflater.inflate(R.layout.test_parameters, container, false)
return rootView
}
}
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:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="#+id/settings_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end|bottom"
android:layout_margin="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
test_parameters.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"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="#+id/result"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#string/testing_method_title"
android:layout_marginTop="16dp"
android:layout_marginStart="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Spinner
android:id="#+id/methods"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:entries="#array/methods"
android:minHeight="48dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/result" />
</androidx.constraintlayout.widget.ConstraintLayout>
You're getting that binding object by calling ActivityMainBinding#inflate - ActivityMainBinding is automatically generated from activity_main.xml (hence the name). It only contains bindings for the views in that layout file (and anything it includes but that's more advanced) - right now you only have one view with an id, settings_button. That's why settingsButton is the only property you can see in the binding file.
You can use findViewById to search the view hierarchy for Fragments' views - but that's generally a bad idea, an Activity shouldn't know anything about the Fragment's internal workings or layout. The Fragment should handle all that itself, and either pass data to its host activity directly, or put the data somewhere the Activity can access it (like a ViewModel, or in a Navigation backstack entry)
Basically put all your functionality inside the DialogFragment - you can use TestParametersBinding to inflate the dialog's layout in the same way you did in your activity, and you can access all its views in there, and handle whatever it is the dialog does. The activity should just show it, and do something with the result it sends back
You will have to create a class extending DialogFragment. There you need to set the id.
Why is val rv: RecyclerView = findViewById(R.id.material_list_rv) returning null?
MainActivity
class MainActivity : AppCompatActivity() {
var adaptor: MaterialListAdaptor = MaterialListAdaptor()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
supportFragmentManager.commit {
setReorderingAllowed(true)
add<MaterialListFragment>(R.id.fragment_container_view)
}
}
initRecyclerView()
setRecyclerViewData()
}
private fun initRecyclerView(){
val rv: RecyclerView = findViewById(R.id.material_list_rv)
rv.layoutManager = LinearLayoutManager(this#MainActivity)
rv.adapter = adaptor
}
private fun setRecyclerViewData(){
val l = DataSource.createMaterialList()
adaptor.setDataSet(l)
}
}
activity_main
<?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"
tools:context=".MainActivity">
<androidx.fragment.app.FragmentContainerView
android:id="#+id/fragment_container_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
MaterialListFragment
class MaterialListFragment : Fragment() {
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
return inflater.inflate(R.layout.fragment_material_list, container, false)
}
}
fragment_material_list
<?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"
tools:context=".MaterialListFragment">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/material_list_rv"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="#layout/material_list_rv_list_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
The problem
You placed the call to findViewById in MainActivity.onCreate. It's too early for that, the Fragment's view hasn't been created at that point.
You should be looking for the view in your MaterialListFragment. If you call findViewById in onCreateView, it will work. Like this:
val root = inflater.inflate(R.layout.fragment_material_list, container, false)
val rv = root.findViewById(R.id. material_list_rv)
// Do whatever (eg assign rv to a field in the Fragment to use it later)
return root
Some advice
Even if your code worked, try to have Activities, Fragments and custom Views manage their own children.
If the upper containers dive deep into their children, it's very easy to make mistakes (like the one here!) and you lose the ability to update internal details of Fragments and Views without breaking the containing Activity.
Activity/Fragment lifecycle chart
To orient yourself, keep this in mind (though this diagram is EXTREMELY simplified):
You are looking for RecylerView in MainActivity, where it does not exist.
Instead you should try to access it in FragmentActivity.
I'm currently updating the source code with the ViewBindings but I'm not getting them to work for child layouts of the other modules which was working previously. I'm on Android Studio 4.1.3. I added viewBinding.enabled = true to app modules build.gradle. But when I try to access a button from the child layouts, it does not give any error but it does not perform the operation also.
main_fragment.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:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#000000">
<com.playerview.TestPlayerView
android:id="#+id/testPlayerView"
android:layout_width="wrap_content"
android:layout_height="0dp"
app:v3PlaybackEnabled="true"
app:always_show_go_to_live="true"
app:extra_overlay_layout = "#array/extra_overlays"
app:server_side_ad_overlay_index = "1"
app:hide_play_pause_on_live="true"
app:show_partner_logo="true"
app:hide_seek_controllers_on_live="true"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:show_subtitle_on_full_screen="true" />
</androidx.constraintlayout.widget.ConstraintLayout>
FragmentBindingProvider.kt
class FragmentBindingProvider private constructor(
val btnPlayNext: Button?,
val testPlayerView: TestPlayerView,
val txtPlaylistPosition: TextView?,
val btnMute: ToggleButton?,
private val root: View
) : ViewBinding {
override fun getRoot(): View = root
companion object {
fun inflate(isLogoView: Boolean, inflater: LayoutInflater, container: ViewGroup?): FragmentBindingProvider {
return if (isLogoView) initLogo(inflater, container) else init(inflater, container)
}
private fun initLogo(inflater: LayoutInflater, container: ViewGroup?): FragmentBindingProvider {
val binding = FragmentLogoBinding.inflate(inflater, container, false)
return FragmentBindingProvider(
btnPlayNext = binding.btnPlayNext,
testPlayerView = binding.testPlayerView,
txtPlaylistPosition = binding.txtPlaylistPosition,
btnMute = binding.testPlayerView.findViewById(R.id.btnMute),
root = binding.root
)
}
}
}
player_video_controls.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:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
tools:visibility="visible">
<include
android:id="#+id/player_volume_layout"
layout="#layout/view_controls_volume"
android:layout_width="#dimen/player_ad_volume_width"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="#id/bottom_bar"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="#id/bottom_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>
view_controls_volume.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:background="#color/transparent_black_percent_80">
<ToggleButton
android:id="#+id/btnMute"
android:layout_width="#dimen/top_controls_icon_width"
android:layout_height="#dimen/top_controls_icon_height"
android:layout_gravity="center"
android:background="#drawable/ic_volume"
android:contentDescription="#null"
android:scaleType="center"
android:checked="true"
android:textOff=""
android:textOn="" />
</FrameLayout>
MainFragment.kt
override fun onStart() {
super.onStart()
observeViewModel()
/*Here calling view of child layout*/
binding.btnMute?.setOnCheckedChangeListener { v, isChecked ->
if (isChecked && binding.testPlayerView.isMuted()) {
binding.testPlayerView.unmute()
} else {
binding.testPlayerView.mute()
}
}
}
If anyone gets idea please let me know.
Only answers the problem that the included layout is in another module:
For a quick fix if the player_video_controls.xml is not in the same module as player_video_controls.xml, add it to the same module or instead of including it, add the layout itself (which is awful but it works).
But, if you have many instances of this issue I suggest you read this answer and try to enable ViewbBinding in every module of your project that is a direct or indirect dependency of the module with this problem.
bindig.playerVolumeLayout.btnMute your can access like this
Instead of :
binding.testPlayerView.findViewById(R.id.btnMute),
In your FragmentBindingProvider.kt:
binding.testPlayerView.btnMute
Because after you do binding.btnMute? and may be btnMute is null and setOnCheckedChangeListener won't be trigger.
If it's not the solution remove your val btnMute: ToggleButton? to val btnMute: ToggleButton and remove binding.btnMute? by binding.btnMute. But nromaaly viewbinding is null safe so you have a value for your button.
I need to draw dynamic buttons inside a foreach loop that retrieve data from my anko sqlite, the foreach only enter once and breaks and only draw one button in my layout, what I doing wrong? my code is this:
fun loadZones (ctx: Context, update: String, view: View, layout: LinearLayout) {
val zonesParser = rowParser{idzone: Int, zone: String -> Pair(idzone, zone)}
for (it in ctx.database.use {
select("tableplan")
.distinct()
.column("idzone")
.column("zone")
.orderBy("zone")
.parseList(zonesParser)
}) {
val layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT)
val btnZone = layoutInflater.inflate(R.layout.zones_item, null) as MaterialButton
btnZone.text = it.second
btnZone.id = it.first
layout.addView(btnZone, layoutParams)
Log.e("PAIR", "FIN DEL CICLO")
continue
}
}
The data that retrieves from my query is this:
(2, LARRY)
(1, MADISON)
That's my activity, I need to draw the buttons in "lytZonesButtons" id
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 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" tools:context=".TablePlanFragment">
<com.google.android.material.appbar.AppBarLayout android:layout_width="match_parent"
android:layout_height="wrap_content" android:elevation="2dp"
tools:targetApi="lollipop" app:liftOnScroll="true">
<androidx.appcompat.widget.Toolbar
android:id="#+id/toolbarTablePlan"
style="#style/com.madison.Toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:title="#string/table_title_module">
</androidx.appcompat.widget.Toolbar>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_marginTop="56dp"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:orientation="horizontal"
android:background="#color/orangeLighter"
android:gravity="center_vertical"
android:padding="5dp" android:id="#+id/lytZonesButtons" />
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="112dp"
android:padding="5dp">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rc_tableplan"
android:layout_width="match_parent"
android:layout_height="match_parent"
/>
</androidx.core.widget.NestedScrollView>
</FrameLayout>
and that's my button template that I called "zones_item":
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" style="#style/com.madison.AppButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="#style/TextAppearance.MaterialComponents.Subtitle2"
tools:text="MADISON"
tools:targetApi="lollipop"
android:layout_margin="5dp"
/>
EDIT: I found the solution!
I don't now why my layout instance in the twice iteration of my loop throws NullPointerException but not shows in the log cat, my solution was put the loop code in onCreateView function, this is the code:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.activity_tableplan, container, false)
val iActivity = (activity as AppCompatActivity)
iActivity.setSupportActionBar(view.toolbarTablePlan)
iActivity.supportActionBar?.setDisplayShowTitleEnabled(true)
// view.rc_tableplan.setHasFixedSize(true)
// val gridLayoutManager = GridLayoutManager(context, 2, GridLayoutManager.HORIZONTAL, false)
// view.rc_tableplan.layoutManager = gridLayoutManager
val response = loadTablePlan(this.context!!, "no")
if (response.trim().toUpperCase() == "SUCCESS") {
val zonesParser = rowParser{idzone: Int, zone: String -> Pair(idzone, zone)}
for (zone in this.context!!.database.use {
select("tableplan")
.distinct()
.column("idzone")
.column("zone")
.orderBy("zone")
.parseList(zonesParser)
}) {
val layout:LinearLayout = view.lytZonesButtons
layout.let {
val btnZone = layoutInflater.inflate(R.layout.zones_item, layout, false) as MaterialButton
btnZone.text = zone.second
btnZone.id = zone.first
btnZone.requestLayout()
layout.addView(btnZone)
Log.e("PAIR", "FIN DEL CICLO")
}
}
}
return view
}
Thanks a lot for all people that tried help me, some admin can close my question please.
The hint is only one button is showing. Your trying to inflate the same view twice in the same spot.
You need to add an empty linearlayout in your xml. And in your loop change the buttonz..
var btnZone = findViewById(R.layout.btnZone)
button.text = "Pair"
btnZone.addView(button, layoutParams)
That's not the exact code (and probably not even the right syntax) but it shows you how you need to modify your loop.
Basicly you were attempting to inflate the same instance of the same view. When really your not inflating any views this way your just adding views.
Note
If you have a linearlayout in your xml when you add another button view to it it will add it below it. If you set the layout orientation to horizontal the button view then gets added beside the other one.
here's a link to an example.
Sorry I would make sure my code matched your code and variables with proper syntax but I am at work.
In my application I'm trying to create 2 fragments with ViewPagers. The fragments have 3 tabs/pages each with RecyclerViews and they are supposed to be backed by a database, so I'm keeping them in a List.
class MainActivity : AppCompatActivity() {
val fragments = listOf(SwipeFragment(), SwipeFragment())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
supportFragmentManager.beginTransaction().replace(R.id.placeholder, fragments[0]).commit()
button1.setOnClickListener {
supportFragmentManager.beginTransaction().replace(R.id.placeholder, fragments[0]).commit()
}
button2.setOnClickListener {
supportFragmentManager.beginTransaction().replace(R.id.placeholder, fragments[1]).commit()
}
}
}
The problem is that after navigating away from the SwipeFragment and going back, the view seems empty. Then, if you navigate to e.g. the leftmost page, the rightmost one "appears" (when you go to it).
This results in the middle page staying empty. (the reason for it being that FragmentStatePagerAdapter keeps only the current page and 2 adjacent ones by default. The middle one doesn't get refreshed in a layout with 3 tabs - I tried it also with 5 tabs and I'm able to bring all of them back by going back and forth).
After some debugging I saw that the fragments that represent pages don't get removed from the FragmentManager, but the main SwipeFragment is. I can't get my head around how FragmentManager really works, even after reading almost all of the source code.
If I were able to somehow safely remove the fragments from the FragmentManager, it may work.
I've tried some techniques for saving state, but the framework does that automatically (which might actually be the cause of this problem).
I'm just a beginner in Android, so there's probably a better way to do this anyway. I'll leave the rest of the files here for reference.
activity_main.xml
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context="com.example.test.MainActivity">
<FrameLayout
android:id="#+id/placeholder"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="0"
android:orientation="horizontal">
<Button
android:id="#+id/button1"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button1" />
<Button
android:id="#+id/button2"
style="?android:buttonBarButtonStyle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="button2" />
</LinearLayout>
</LinearLayout>
SwipeFragment.kt
class SwipeFragment : Fragment() {
// TODO: set up and keep the database here
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.fragment_swipe, container, false)
view.tab_layout.setupWithViewPager(view.pager)
view.pager.adapter = DummyPagerAdapter(activity.supportFragmentManager, index)
return view
}
}
fragment_swipe.xml
<android.support.v4.view.ViewPager
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/pager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.test.SwipeFragment">
<android.support.design.widget.TabLayout
android:id="#+id/tab_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</android.support.v4.view.ViewPager>
ItemListFragment.kt
class ItemListFragment() : Fragment() {
// Or keep the database here?
// Probably not the best idea though
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater!!.inflate(R.layout.fragment_item_list, container, false)
if (view is RecyclerView) {
view.layoutManager = LinearLayoutManager(view.context)
view.adapter = MyItemRecyclerViewAdapter(DummyContent.ITEMS)
}
return view
}
}
DummyPagerAdapter.kt
class DummyPagerAdapter(manager: FragmentManager, val parentIndex: Int) :
FragmentStatePagerAdapter(manager) {
override fun getItem(position: Int): Fragment = ItemListFragment()
override fun getPageTitle(position: Int): CharSequence = "${ position + 1 }"
override fun getCount(): Int = 3
}
And a basic implementation of RecyclerView.Adapter generated by Android Studio
MyItemRecyclerViewAdapter.kt
class MyItemRecyclerViewAdapter(val values: List<DummyItem>) :
RecyclerView.Adapter<MyItemRecyclerViewAdapter.ViewHolder>() {
...
}
fragment_item_list.xml
<android.support.v7.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.test.ItemListFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="LinearLayoutManager"
tools:context="com.example.test.ItemListFragment"
tools:listitem="#layout/fragment_item" />
You should use childFragmentManager instead of activity.supportFragmentManager inside SwipeFragment.
view.pager.adapter = DummyPagerAdapter(childFragmentManager, index)
The difference between getSupportFragmentManager() and getChildFragmentManager() is discussed here.
Basically, the difference is that Fragment's now have their own internal FragmentManager that can handle Fragments. The child FragmentManager is the one that handles Fragments contained within only the Fragment that it was added to. The other FragmentManager is contained within the entire Activity.
In my case childFragmentManager didn't help. I think we can use a pattern "Observer" to update existing fragments of ViewPager with new data when return from another fragment.