在Fragments中保存和恢复ListView(livedata)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在Fragments中保存和恢复ListView(livedata)相关的知识,希望对你有一定的参考价值。

我正在尝试制作Todo应用程序。我已经成功地在片段中实现了livesata和listview(片段默认来自项目快速入门模板)。我无法解决的问题是保存那些待办事项,以便他们在再次启动应用程序时仍然存在。

在堆栈和博客上浏览了大量的答案并阅读了整个生命周期,但我仍然没有得到它。我终于放弃了,这就是我最终得到的代码(不工作):

FragmentLifeCycle保存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()")
    }

}

抛出nullpointer:

空对象引用上的'android.os.Parcelable android.widget.ListView.onSaveInstanceState()'

Main_Activity清除了大量评论不起作用的解决方案:

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
            }
        }
    }
}

不确定它是否有用,但这就是TodoStore.it的样子:

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))
    }

}
答案

如果我理解正确,您需要在应用程序中添加持久层。加载ListView时尝试使用Room Database。 SavedInstanceState有一些限制,不应该用于保存大量数据或复杂对象。

Android Persistence

Room Database

希望这有帮助。

另一答案

如果您需要保存用户在listView中的位置,请在onSaveInstanceState()fragment方法中仅保存Int中的Int。如果你想在listView中保存数据,你不需要这样做,因为Android已经这样做了,你只需要在onActivityCreated中放入loadData(你的代码初始化数据并将适配器设置到listView),只是恢复在onViewStateRestored()的位置。

以上是关于在Fragments中保存和恢复ListView(livedata)的主要内容,如果未能解决你的问题,请参考以下文章

返回 ListView 时保持/保存/恢复滚动位置

返回ListView时保持/保存/恢复滚动位置

使用 Fragments 和 ViewPager 一段时间后,Android 应用程序崩溃

当用户按下返回按钮关闭活动时保存和恢复活动状态

Android Fragment使用 嵌套Fragments (Nested Fragments) 的使用及常见错误

Android Fragment使用 嵌套Fragments (Nested Fragments) 的使用及常见错误