I have a SearchFragment, inside it is an EditText (for entering a query), a TabLayout and a ViewPager2 with three Fragments (StreamsFragment, PlaylistsFragment, TracksFragment).
When entering a query, I want to send it to the currently selected fragment, so that the search and display logic is in the corresponding fragment.
So the question is, how do I pass the entered queries into fragments in ViewPager?
I had an idea to pass a Bundle with Flow or LiveData as arguments, and emit in SearchFragment and collect Flow in nested fragments. But Flow and LiveData, as expected, do not implement Parcelize.
Is there any option to implement this behavior?
fragment_search.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/coordinator"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@android:color/transparent"
android:fitsSystemWindows="true"
app:elevation="0dp">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/toolbarLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/primary"
android:fitsSystemWindows="true"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_scrollFlags="scroll|enterAlways">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/searchTextInputLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:background="@android:color/transparent"
app:cursorColor="@color/secondary"
app:endIconMode="clear_text"
app:endIconTint="@color/secondary"
app:hintEnabled="false"
app:layout_anchor="@id/toolbarLayout"
app:layout_anchorGravity="bottom"
app:layout_collapseMode="pin">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/searchEditText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/search_view_background"
android:hint="@string/search_hint"
android:imeOptions="actionSearch"
android:inputType="text"
android:maxLines="1"
android:textColor="@color/textSubTitle"
android:textColorHint="@color/secondary" />
</com.google.android.material.textfield.TextInputLayout>
</com.google.android.material.appbar.AppBarLayout>
</com.google.android.material.appbar.AppBarLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_anchor="@id/appBar"
app:layout_anchorGravity="bottom"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
<com.google.android.material.tabs.TabLayout
android:id="@+id/tabLayout"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginHorizontal="12dp"
android:background="@android:color/transparent"
app:tabBackground="@drawable/chips_button_background"
app:tabGravity="center"
app:tabIndicator="@null"
app:tabIndicatorColor="@android:color/transparent"
app:tabIndicatorHeight="0dp"
app:tabMode="scrollable"
app:tabRippleColor="@null"
app:tabTextAppearance="@style/SemiBold_13"
app:tabTextColor="@color/chips_button_text_color_state"
app:tabUnboundedRipple="false" />
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/viewpager"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginTop="8dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
</LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
SearchFragment.kt
class SearchFragment : Fragment(R.layout.fragment_search) {
private var _binding: FragmentSearchBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = FragmentSearchBinding.bind(view)
initVP()
inputListener()
}
private fun initVP() {
binding.viewpager.adapter = VpAdapter(this, "1")
TabLayoutMediator(binding.tabLayout, binding.viewpager) { tab, position ->
when (position) {
0 -> tab.text = getString(stringResources.bottom_bar_streams)
1 -> tab.text = getString(stringResources.playlists)
2 -> tab.text = getString(stringResources.tracks)
}
}.attach()
}
private fun inputListener() {
binding.searchEditText.doAfterTextChanged { text ->
//How send text to Fragments?
}
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Adapter for ViewPager (currently the userId parameter is passed, this is for example, this approach is used in another part of the application)
class VpAdapter(
fragment: Fragment,
private val userId: String
) : FragmentStateAdapter(fragment) {
override fun getItemCount(): Int = 3
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> StreamsFragment().apply {
arguments = bundleOf(Pair(StreamsFragment.KEY_USER_ID, userId))
}
1 -> PlaylistsFragment().apply {
arguments = bundleOf(Pair(PlaylistsFragment.KEY_USER_ID, userId))
}
2 -> TracksFragment().apply {
arguments = bundleOf(Pair(TracksFragment.KEY_USER_ID, userId))
}
else -> throw IllegalStateException("${this.javaClass.name} can`t create fragment: Fragment for position $position not found")
}
}
}