How can I tell the adapter ViewPager2 to redraw elements? - android

My app contains viewpager and tablayout. It requests data from the server, sorts and gives each tab its own set of information. I don't want to have a request to the network every time I select a tab, so I made a host fragment that makes a request to the network and creates tabs. But when the data arrives, the view is already rendered. I give the data through the bundle to the ViewPagerAdapter. The first tab does not update the data. How can I tell the adapter to redraw a particular element?
HostFragment
private val tabsTitles by lazy(LazyThreadSafetyMode.NONE) {
listOf("All", "Analytics", "Android", "Management")
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val departmentTabLayout = view.findViewById<TabLayout>(R.id.departmentTabLayout)
val departmentViewPager = view.findViewById<ViewPager2>(R.id.departmentViewPager)
val adapter = TabsAdapter(childFragmentManager, lifecycle)
departmentViewPager.adapter = adapter
attachTabs(departmentTabLayout, departmentViewPager)
departmentHostViewModel.listUsers.observe(viewLifecycleOwner, {
adapter.getUsers(it.items)
//adapter.notifyDataSetChanged()
})
}
private fun attachTabs(departmentTabLayout: TabLayout, departmentViewPager: ViewPager2) {
TabLayoutMediator(departmentTabLayout, departmentViewPager) { tab, position ->
tab.text = tabsTitles[position]
}.attach()
}
TabsAdapter
class TabsAdapter(
fm: FragmentManager,
lifecycle: Lifecycle,
) : FragmentStateAdapter(fm, lifecycle) {
private val listDepartments = listOf(
"all",
"analytics",
"android",
"management"
)
private val listUsers: MutableList<User> = mutableListOf()
override fun createFragment(position: Int): Fragment {
val departmentFragment = DepartmentFragment()
return when (position) {
0 -> {
departmentFragment.arguments = bundleOf(DepartmentFragment.LIST_USERS to listUsers)
return departmentFragment
}
else -> {
departmentFragment.arguments =
bundleOf(DepartmentFragment.LIST_USERS to listUsers.filter { it.department == listDepartments[position] })
return departmentFragment
}
}
}
override fun getItemCount(): Int {
return listDepartments.count()
}
fun getUsers(listUsers2: List<User>) {
listUsers.addAll(listUsers2)
notifyDataSetChanged()
}
}

Related

Kotlin (android studio) - changing tab by clicking doesn't change button on selected fragment, where swiping to the tab works

I'm new to Kotlin and Android programming.
I Have 2 tabs using TabLayout and ViewPager2.
In my main activity xml I have edit text widget. When I enter text and push ENTER the program needs to take the 'value' of the 'key' from edit text and add it as a button in the two tabs (fragments).
Now, if I'm in the first tab - I can't seem to add button to the secont one.
So I tried to add the button only after selecting the second tab, but clicking the tab seems to not work where swiping to the other tab works as planned.
Please help me to:
Editing another fragment where its now in 'focus'
fixing the clicking/swiping problem described.
Thanks!
My MainActivity.kt:
class MainActivity : AppCompatActivity() {
//declare all collections of barcode, items and changes
var mConstantBarcodeMap = Constants.constantBarcodeMap
private var usedBarcodeItems = mutableMapOf<String,String>()
// declare binding object for all layouts
private lateinit var bindingMain : ActivityMainBinding
// declare tab and viewpager2 instances for building tabbed application
private lateinit var tabLayout : TabLayout
private lateinit var viewPager2 : ViewPager2
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// make binding object for all views
bindingMain = ActivityMainBinding.inflate(layoutInflater)
val viewMain = bindingMain.root
setContentView(viewMain)
getWindow().setBackgroundDrawableResource(R.drawable.background_iphone2);
//get tab layout and viewPager2 from xml file
tabLayout = findViewById(R.id.tab_layout)
viewPager2 = findViewById(R.id.view_pager_2)
val adapter = ViewPagerAdapter(supportFragmentManager, lifecycle)
viewPager2.adapter = adapter
TabLayoutMediator(tabLayout, viewPager2) { tab, position ->
when (position) {
0 -> tab.text = "Add"
1 -> tab.text = "Remove"
}
}.attach()
// declare tab selected listener
tabLayout.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab) {
tabChanged(tabLayout.selectedTabPosition)
}
override fun onTabUnselected(tab: TabLayout.Tab) {}
override fun onTabReselected(tab: TabLayout.Tab) {}
})
// Make edit text listener
val editTextInput = findViewById<EditText>(R.id.edit_text_input)
editTextInput.setOnKeyListener(View.OnKeyListener { v, keyCode, event ->
if (keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_UP) {
actionWithTextAfterEnter()
return#OnKeyListener true
}
false
})
}
// method to add Button to addFragment
private fun actionWithTextAfterEnter() {
when (tabLayout.selectedTabPosition) {
0 -> addTabActions()
1 -> removeTabActions()
}
}
private fun tabChanged(numOfTab: Int) {
when (numOfTab) {
0 -> switchedToAddTab()
1 -> {
val isNull = (findViewById<LinearLayout>(R.id.ll_fragment_remove) != null)
Toast.makeText(this, isNull.toString(), Toast.LENGTH_SHORT).show()
if (findViewById<LinearLayout>(R.id.ll_fragment_remove) != null) {
switchedToRemoveTab()
}
}
}
}
private fun switchedToAddTab() {
return
}
private fun switchedToRemoveTab() {
val layout = findViewById<LinearLayout>(R.id.ll_fragment_remove)
// removes all widget from add fragment
layout.removeAllViews()
// remake all widget to add fragment from collection
for (value in usedBarcodeItems.values) {
layout.addView(createButton(value))
}
}
private fun addTabActions() {
// checking if barcode is in mConstantBarcodeMap
val etText : String = bindingMain.editTextInput.text.toString()
val barcode = etText.dropLast(1)
val isInBarcodeMap : Boolean = mConstantBarcodeMap.containsKey(barcode)
val isInBarcodeItemMap: Boolean = usedBarcodeItems.containsKey(barcode)
val layout = findViewById<LinearLayout>(R.id.ll_fragment_add)
if (isInBarcodeMap && !isInBarcodeItemMap) {
usedBarcodeItems[barcode] = mConstantBarcodeMap[barcode].toString()
// removes all widget from add fragment
layout.removeAllViews()
// remake all widget to add fragment from collection
for (value in usedBarcodeItems.values) {
layout.addView(createButton(value))
}
} else if (isInBarcodeMap && isInBarcodeItemMap) {
showWarningToast("This Item is Already on the List!")
} else if (!isInBarcodeMap) {
showWarningToast("This Item is not in Barcode List!")
}
bindingMain.editTextInput.text.clear()
}
private fun removeTabActions() {
return
}
private fun createButton(buttonText : String) : Button {
// declare and configure button widget
val buttonItem = MaterialButton(this)
val params: LinearLayout.LayoutParams = LinearLayout.LayoutParams(
LinearLayoutCompat.LayoutParams.MATCH_PARENT,
LinearLayoutCompat.LayoutParams.WRAP_CONTENT)
params.setMargins(20, 10, 20, 10)
buttonItem.layoutParams = params
buttonItem.text = buttonText
buttonItem.textSize = 20f
buttonItem.setTextColor(Color.BLACK)
buttonItem.setBackgroundColor(ContextCompat.getColor(this, R.color.yellow_500))
return buttonItem
}
private fun showWarningToast(warning: String) {
Toast.makeText(this,warning,Toast.LENGTH_LONG).show()
}
}
Recording of app:
https://imgur.com/oM9T5Ak
notice the true/false toast:
Toast.makeText(this, isNull.toString(), Toast.LENGTH_SHORT).show()
Maybe you need some changes in the adapter code of viewPager
Let me share my code Here
With tab layout and fragment
Here is the TabAdapter code
class TabAdapter : FragmentStateAdapter {
var fragments = arrayListOf<Fragment>()
constructor(fragmentActivity: FragmentActivity, fragments: ArrayList<Fragment>) : super(fragmentActivity) {
this.fragments = fragments
}
constructor(fragmentManager: FragmentManager, lifecycle: Lifecycle, fragments: ArrayList<Fragment>) : super(
fragmentManager,
lifecycle
) {
this.fragments = fragments
}
override fun getItemCount(): Int {
return fragments.size
}
override fun createFragment(position: Int): Fragment {
return fragments[position]
}
}
Attaching Fragment
private fun setUpViewPager(fragments: ArrayList<Fragment>, titles: ArrayList<String>) {
fragmentAdapter = TabAdapter(this, fragments)
bindingMain.viewPager2.offscreenPageLimit = fragments.size
bindingMain.viewPager2.adapter = fragmentAdapter
bindingMain.viewPager2.isSaveEnabled = false
TabLayoutMediator(bindingMain.tabLayout, bindingMain.viewPager2) { tab, position ->
tab.text = titles[position]
}.attach()
}

Android viewpager2 tab layout with FragmentStateAdapter

I am using ViewPager2 with Tab Layout. Here is my MainFragment code -
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.viewPager.adapter = MyPagerAdapter(requireActivity())
TabLayoutMediator(
binding.tabLayout, binding.viewPager
) { tab, position ->
binding.viewPager.setCurrentItem(0, true)
when (position) {
0 -> tab.text = “Tab A”
1 -> tab.text = “Tab B”
}
}.attach()
}
private class MyPagerAdapter(fragmentActivity: FragmentActivity) :
FragmentStateAdapter(fragmentActivity) {
private val items = 2
override fun getItemCount(): Int {
return items
}
override fun createFragment(position: Int): Fragment = when (position) {
0 -> FragmentA()
1 -> FragmentB()
else -> FragmentA()
}
}
I have 2 questions here -
override fun createFragment(position: Int): Fragment creates new instance of child fragments every time the MainFragment view is created. Is there no way to re-use an already existing instance of child fragment?
In my Navigation graph, I have the MainFragment and its children FragmentA and FragmentB. Why cant I use the Navigation action to open children from its parent? If yes, override fun createFragment(position: Int): Fragment needs a Fragment to be returned and findNavController().navigate() does not return anything. How do I do this?
you can follow this sample code, may this will help you
class TvShowsFragment : Fragment(R.layout.tvshows_fragment) {
private var _binding: TvshowsFragmentBinding? = null
private val binding get() = _binding!!
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
_binding = TvshowsFragmentBinding.bind(view)
setUpViewPager()
setHasOptionsMenu(true)
}
private fun setUpViewPager() {
val viewPager = binding.vpTvShows
val tab = binding.tlTvShows
val adapterTv = TvAdapter(this)
viewPager.adapter = adapterTv
TabLayoutMediator(tab, viewPager) { tabText, position ->
tabText.text = when (position) {
0 -> getString(R.string.title_tvAiringToday)
1 -> getString(R.string.title_tvOnTheAir)
2 -> getString(R.string.title_popular)
3 -> getString(R.string.title_topRated)
else -> getString(R.string.title_tvAiringToday)
}
}.attach()
}
private inner class TvAdapter(fm: Fragment) : FragmentStateAdapter(fm) {
override fun getItemCount(): Int = 4
override fun createFragment(position: Int): Fragment {
return when (position) {
0 -> TvAiringTodayFragment()
1 -> TvOnTheAirFragment()
2 -> TvPopularFragment()
3 -> TvTopRatedFragment()
else -> TvAiringTodayFragment()
}
}
}

Getting “java.lang.IllegalArgumentException: No view found for id” in ViewPager's Fragment that in RecyclerView

I am getting exception:
E/MessageQueue-JNI: java.lang.IllegalArgumentException: No view found for id 0x7f0a0554 (ua.com.company.app:id/vpBanners) for fragment BannerItemFragment{30698be} (4c80b228-4303-4c80-b99d-a55b8359b8c2) id=0x7f0a0554}
My hierarchy looks like so:
My adapter for vpHome:
class HomeViewPagerAdapter(fm: FragmentManager) : FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
private val mFragmentList: MutableList<Fragment> = ArrayList()
private val mFragmentTitleList: MutableList<String> = ArrayList()
override fun getItem(position: Int): Fragment {
return mFragmentList[position]
}
override fun getCount(): Int {
return mFragmentList.size
}
override fun getPageTitle(position: Int): CharSequence? {
return mFragmentTitleList[position]
}
fun addFragment(fragment: Fragment, title: String) {
mFragmentList.add(fragment)
mFragmentTitleList.add(title)
}
}
And I apply it in this way:
private fun setupViewPager(viewPager: DisableSwipeViewPager) {
vpAdapter = HomeViewPagerAdapter(childFragmentManager).apply {
addFragment(ForYouFragment(), "for you")
addFragment(AnotherFragment1(), "a1")
addFragment(AnotherFragment2(), "a2")
}
viewPager.adapter = vpAdapter
}
Next, my SnapBannersCarouselViewHolder to handle items with ViewPager inside:
class SnapBannersCarouselViewHolder(
private val mBinding: HomeFragmentItemSnapBannersCarouselBinding
) : RecyclerView.ViewHolder(mBinding.root) {
companion object {
// ...
fun newInstance(
inflater: LayoutInflater,
parent: ViewGroup,
onMoreInteractionListener: ((infoBlockId: String) -> Unit)?
): SnapBannersCarouselViewHolder {
val binding =
HomeFragmentItemSnapBannersCarouselBinding.inflate(inflater, parent, false)
return SnapBannersCarouselViewHolder(
binding,
onMoreInteractionListener
)
}
}
fun bind(item: SnapBannersItem, fragmentManager: FragmentManager) {
// ...
val adapter = BannerPagesAdapter(fragmentManager, item.banners)
with(mBinding.vpBanners) {
// ...
this.adapter = adapter
offscreenPageLimit = 3
}
}
}
My RecyclerView adapter ForYouContentAdapter:
class ForYouContentAdapter(
var data: List<HomeBaseItem> = emptyList(),
var fragmentManagerRetriever: () -> FragmentManager
) : BaseRecyclerViewAdapter<HomeBaseItem>(data) {
enum class ViewType(val value: Int) {
// ...
SNAP_BANNERS(6)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
// ...
ViewType.SNAP_BANNERS.value -> SnapBannersCarouselViewHolder.newInstance(
mInflater!!,
parent,
onBannersMoreInteractionListener // todo possibly change it
)
else -> throw RuntimeException("Can not create view holder for undefined view type")
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (getItemViewType(position)) {
// ...
ViewType.SNAP_BANNERS.value -> {
val vHolder = holder as SnapBannersCarouselViewHolder
vHolder.bind(
getItem(position) as SnapBannersItem,
fragmentManagerRetriever.invoke()
)
}
}
}
}
And my fragmentManagerRetriever implementation in ForYouFragment looks like so:
private val fragmentManagerRetriever: () -> FragmentManager = {
childFragmentManager
}
My BannerItemFragment code:
class BannerItemFragment : Fragment() {
private lateinit var mBinding: HomeFragmentSnapBannerItemBinding
companion object {
// ...
fun newInstance(
item: RectWebViewItem
): BannerItemFragment {
return BannerItemFragment()
}
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
mBinding = HomeFragmentSnapBannerItemBinding.inflate(inflater, container, false)
return mBinding.root
}
// ...
}
In every place where I need to create fragments inside other fragments I am using childFragmentManager.
And when I open ForYouFragment first time, it works normally. My item with ViewPager works normally. But when I replace fragment in Activity's container (adding to back stack) being on ForYouFragment and then return back (back on HomeFragment because ForYouFragment inside HomeFragment), I am getting error.
To replace fragments I am using this method inside ForYouFragment:
private fun showAnotherFragment() {
ActivityUtils.replaceFragmentToActivity(
requireActivity(),
SomeFragment.newInstance(),
true
)
}
And ActivityUtils code:
object ActivityUtils {
fun replaceFragmentToActivity(
activity: FragmentActivity,
fragment: Fragment,
addToBackStack: Boolean
) {
replaceFragmentToActivity(activity, fragment, addToBackStack, containerId = R.id.fragmentContainer)
}
fun replaceFragmentToActivity(
activity: FragmentActivity,
fragment: Fragment,
addToBackStack: Boolean,
containerId: Int
) {
val fragmentManager = activity.supportFragmentManager
val transaction = fragmentManager.beginTransaction()
transaction.replace(containerId, fragment)
if (addToBackStack) {
transaction.addToBackStack(null)
}
transaction.commitAllowingStateLoss()
}
}
Please, help me understand why I am getting this exception?
UPD
Adapter for ViewPager inside RecyclerView:
class BannerPagesAdapter(
fragmentManager: FragmentManager,
var data: List<RectWebViewItem>
) : FragmentStatePagerAdapter(
fragmentManager,
BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT
) {
private var fragments: MutableList<BannerItemFragment> = mutableListOf()
init {
// initial empty fragments
for (i in data.indices) {
fragments.add(BannerItemFragment())
}
}
override fun getItem(position: Int): Fragment {
return BannerItemFragment.newInstance(data[position])
}
override fun getCount(): Int {
return fragments.size
}
override fun instantiateItem(container: ViewGroup, position: Int): Any {
val f = super.instantiateItem(container, position)
fragments[position] = f as BannerItemFragment
return f
}
}
Solved
The problem was that fragments want to be attached to the ViewPager before the ViewPager is attached to its parent. This question outlined here.
So, to solve this problem, I created custom ViewPager:
/**
* Use this ViewPager when you need to place ViewPager inside RecyclerView.
* [LazyViewPager] allows you to set [PagerAdapter] in lazy way. This prevents IllegalStateException
* in case when the fragments want to be attached to the viewpager before the viewpager is
* attached to its parent
*/
class LazyViewPager
#JvmOverloads
constructor(
context: Context,
attrs: AttributeSet? = null
) : ViewPager(context, attrs) {
private var mPagerAdapter: PagerAdapter? = null
override fun onAttachedToWindow() {
super.onAttachedToWindow()
if (mPagerAdapter != null) {
super.setAdapter(mPagerAdapter)
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
super.setAdapter(null)
}
#Deprecated("Do not use this method to set adapter. Use setAdapterLazy() instead.")
override fun setAdapter(adapter: PagerAdapter?) {}
fun setAdapterLazy(adapter: PagerAdapter?) {
mPagerAdapter = adapter
}
}
And then, instead of using setAdapter() I use setAdapterLazy().
Also, it is important to reset adapter to null in onDetachedFromWindow().

In tab layout fragments not showing their layout in androidX

I have a tab layout in which I have included five fragments, but when i click on tabs, the fragments layout is not showed. In my adapter I've extended FragmentPagerAdapter which has FragmentManager and a variable behaviour.
class ViewPagerAdapter(#NonNull fm:FragmentManager, behaviour:Int):
FragmentPagerAdapter(fm, behaviour) {
private val tabs:Array<Fragment> = arrayOf(
Category1Fragment(),
Category2Fragment(),
Category3Fragment(),
Category4Fragment(),
Category5Fragment()
)
#NonNull
override fun getItem(position: Int): Fragment {
return tabs[position]
}
override fun getCount(): Int {
return tabs.size
}
#Nullable
override fun getPageTitle(position: Int): CharSequence? {
return when (position) {
0 -> "Bags"
1 -> "Watches"
2 -> "Shoes"
3 -> "Glasses"
4 -> "Audio"
else -> null
}
}
}
and in my activity I've called adapter through viewpager, but when I debug is telling me that adapter is null, here's my activity code:
val adapter = ViewPagerAdapter(supportFragmentManager, tab_layout.selectedTabPosition)
val viewPager = findViewById<ViewPager>(R.id.view_pager)
viewPager.adapter = adapter
val tab = findViewById<TabLayout>(R.id.tab_layout)
tab.setupWithViewPager(view_pager)
// -------------------------------------------------------------------------------- //
tab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(p0: TabLayout.Tab?) {
}
override fun onTabUnselected(p0: TabLayout.Tab?) {
}
override fun onTabSelected(p0: TabLayout.Tab?) {
viewPager.currentItem = tab.selectedTabPosition
}
})
In the previous versions of android I did the same, but in androidX things are something different. I tried also to use ViewPager2, but it was confusing.
Any help is appreciated!

Saving and Restoring ListView (livedata) in Fragments

I'm trying to make a Todo app. I have succesfully implemented livedata and listview in fragments (fragments are default from the project quickstart template). My problem which I can't resolve is saving those todo's so they are still there upon launching app back again.
Browsed tons of answers on stack and blogs and read about whole lifecycle but I still don't get it. I finally gave up and this is what (not working) code I end up with atm:
FragmentLifeCycle to save "state" of the listOfToDoThings
class FragmentLifeCycle : Fragment() {
private var state: Parcelable? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
Log.d("Lifecycle Info", "onCreate()")
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
Log.d("Lifecycle Info", "onCreateView()")
return inflater.inflate(R.layout.activity_main, container, false)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
Log.d("Lifecycle Info", "onActivityCreated()")
}
override fun onResume() {
super.onResume()
if (state != null) {
Log.i("Lifecycle Info", "onResume finally works")
listOfToDoThings.onRestoreInstanceState(state)
}
Log.d("Lifecycle Info", "onResume()")
}
override fun onPause() {
state = listOfToDoThings.onSaveInstanceState()
super.onPause()
Log.d("Lifecycle Info", "onStop()")
}
}
which throws nullpointer:
'android.os.Parcelable android.widget.ListView.onSaveInstanceState()' on a null object reference
And Main_Activity cleared out of tons of commented not-working solutions:
class MainActivity : AppCompatActivity(){
private var mSectionsPagerAdapter: SectionsPagerAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
setSupportActionBar(toolbar)
// Create the adapter that will return a fragment for each of the three
// primary sections of the activity.
mSectionsPagerAdapter = SectionsPagerAdapter(supportFragmentManager)
// Set up the ViewPager with the sections adapter.
container.adapter = mSectionsPagerAdapter
val fragmentManager = this.supportFragmentManager
val fragmentTransaction = fragmentManager.beginTransaction()
val fragmentLifeCycle = FragmentLifeCycle()
fragmentTransaction.add(R.id.container, fragmentLifeCycle, "Lifecycle Fragment")
fragmentTransaction.commit()
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
// Inflate the menu; this adds items to the action bar if it is present.
menuInflater.inflate(R.menu.menu_main, menu)
return true
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
val id = item.itemId
if (id == R.id.action_settings) {
return true
}
return super.onOptionsItemSelected(item)
}
/**
* A [FragmentPagerAdapter] that returns a fragment corresponding to
* one of the sections/tabs/pages.
*/
inner class SectionsPagerAdapter(fm: FragmentManager) : FragmentPagerAdapter(fm) {
override fun getItem(position: Int): Fragment {
// getItem is called to instantiate the fragment for the given page.
// Return a PlaceholderFragment (defined as a static inner class below).
return PlaceholderFragment.newInstance(position + 1)
}
override fun getCount(): Int {
// Show 3 total pages.
return 4
}
}
/**
* A placeholder fragment containing a simple view.
*/
class PlaceholderFragment : Fragment(), Renderer<TodoModel> {
private lateinit var store: TodoStore
override fun render(model: LiveData<TodoModel>) {
model.observe(this, Observer { newState ->
listOfToDoThings.adapter = TodoAdapter(requireContext(), newState?.todos ?: listOf())
})
}
private fun openDialog() {
val options = resources.getStringArray(R.array.filter_options).asList()
requireContext().selector(getString(R.string.filter_title), options) { _, i ->
val visible = when (i) {
1 -> Visibility.Active()
2 -> Visibility.Completed()
else -> Visibility.All()
}
store.dispatch(SetVisibility(visible))
}
}
private val mapStateToProps = Function<TodoModel, TodoModel> {
val keep: (Todo) -> Boolean = when(it.visibility) {
is Visibility.All -> {_ -> true}
is Visibility.Active -> {t: Todo -> !t.status}
is Visibility.Completed -> {t: Todo -> t.status}
}
return#Function it.copy(todos = it.todos.filter { keep(it) })
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val rootView = inflater.inflate(R.layout.fragment_main, container, false)
rootView.section_label.text = getString(R.string.section_format, arguments?.getInt(ARG_SECTION_NUMBER))
#SuppressLint("SetTextI18n")
when(arguments?.getInt(ARG_SECTION_NUMBER)) {
1 -> rootView.section_name.text = "Daily Life"
2 -> rootView.section_name.text = "Work and College"
3 -> rootView.section_name.text = "Visits"
4 -> rootView.section_name.text = "Shop"
}
store = ViewModelProviders.of(this).get(TodoStore::class.java)
store.subscribe(this, mapStateToProps)
// Add task and then reset editText component
rootView.addNewToDo.setOnClickListener {
store.dispatch(AddTodo(editText.text.toString()))
editText.text = null
}
rootView.filter.setOnClickListener{ openDialog() }
// Press to change status of task
rootView.listOfToDoThings.adapter = TodoAdapter(requireContext(), listOf())
rootView.listOfToDoThings.setOnItemClickListener { _, _, _, id ->
store.dispatch(ToggleTodo(id))
}
// Hold to delete task
rootView.listOfToDoThings.setOnItemLongClickListener { _, _, _, id ->
store.dispatch(RemoveTodo(id))
true
}
return rootView
}
companion object {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private val ARG_SECTION_NUMBER = "section_number"
/**
* Returns a new instance of this fragment for the given section
* number.
*/
fun newInstance(sectionNumber: Int): PlaceholderFragment {
val fragment = PlaceholderFragment()
val args = Bundle()
args.putInt(ARG_SECTION_NUMBER, sectionNumber)
fragment.arguments = args
return fragment
}
}
}
}
Not sure if its usefull but that's how TodoStore.kt looks like:
class TodoStore : Store<TodoModel>, ViewModel(){
private val state: MutableLiveData<TodoModel> = MutableLiveData()
// Start with all tasks visible regardless of previous state
private val initState = TodoModel(listOf(), Visibility.All())
override fun dispatch(action: Action) {
state.value = reduce(state.value, action)
}
private fun reduce(state: TodoModel?, action: Action): TodoModel {
val newState= state ?: initState
return when(action){
// Adds stuff upon creating new todo
is AddTodo -> newState.copy(
todos = newState.todos.toMutableList().apply {
add(Todo(action.text, action.id))
}
)
is ToggleTodo -> newState.copy(
todos = newState.todos.map {
if (it.id == action.id) {
it.copy(status = !it.status)
} else it
} as MutableList<Todo>
)
is SetVisibility -> newState.copy(
visibility = action.visibility
)
is RemoveTodo -> newState.copy(
todos = newState.todos.filter {
it.id != action.id
} as MutableList<Todo>
)
}
}
override fun subscribe(renderer: Renderer<TodoModel>, func: Function<TodoModel, TodoModel>) {
renderer.render(Transformations.map(state, func))
}
}
If I understand correctly you need to add a persistence layer to your application.
Try to use Room Database when load the ListView.
SavedInstanceState has some limitations and it should not be used to save a large amount of data or complex objects.
Android Persistence
Room Database
Hope this help.
If you need to save the position that the user is in the listView, save only the Int in a bundle on the method onSaveInstanceState() of the fragment. If you want to save the data inside the listView, you do not need to do this, because Android already did that, you just need to put the loadData (your code that init the data and set an adapter to the listView) in onActivityCreated and just restore the position in onViewStateRestored().

Categories

Resources