首页 > 解决方案 > 每次应用重启时 onActivityCreated() 中的函数调用都会触发 Observer

问题描述

在我的片段中,我正在观察函数中的实时数据,并且在该观察者中,一些 sharedPreferences 已更改。然后在 onActivityCreated() 内部调用该函数。问题是,每当我重新启动我的应用程序时,都会调用 onActivityCreated() 函数,该函数又调用该函数,该函数又观察实时数据,从而更改我不想要的 sharedPreference 的值.

附上我的片段的代码。

package com.example.expensemanager.ui

import android.app.AlertDialog
import android.content.Context
import android.content.SharedPreferences
import android.graphics.Color
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager
import com.example.expensemanager.R
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import kotlinx.android.synthetic.main.fragment_transaction_list.*
import kotlinx.android.synthetic.main.set_balance_details.view.*
import org.eazegraph.lib.models.PieModel


class TransactionListFragment : Fragment() {
    //declaring the view model
    private lateinit var viewModel: TransactionListViewModel
    var cashAmount:Float = 0F
    var bankAmount:Float = 0F
    override fun onCreate(savedInstanceState: Bundle?){
        super.onCreate(savedInstanceState)

        //setHasOptionsMenu(true)
        //(activity as AppCompatActivity?)!!.setSupportActionBar(addAppBar)
        viewModel = ViewModelProvider(this)
            .get(TransactionListViewModel::class.java)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_transaction_list, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        //recycler view for showing all the transactions
        with(transaction_list){
            layoutManager = LinearLayoutManager(activity)
            adapter = TransactionAdapter {
                findNavController().navigate(
                    TransactionListFragmentDirections.actionTransactionListFragmentToTransactionDetailFragment(
                        it
                    )
                )
            }
        }
        //code for the floating action button in the main screen
        add_transaction.setOnClickListener{
            findNavController().navigate(
                //here id is passed 0 because the transaction is being added the first time
                TransactionListFragmentDirections.actionTransactionListFragmentToTransactionDetailFragment(
                    0
                )
            )
        }

        addAppBar.setOnMenuItemClickListener { menuItem ->
            when(menuItem.itemId){
                R.id.calendar_button -> {
                    findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToCalanderViewFragment())
                    true
                }
                R.id.monthly_cards -> {
                    findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToMonthlyCardsFragment())
                    true
                }
                else -> false
            }
        }
        see_all_transactions.setOnClickListener {
            findNavController().navigate(TransactionListFragmentDirections.actionTransactionListFragmentToAllTransactionsFragment())
        }


        //submitting the new list of upcoming Transactions after getting it from the db
        viewModel.upcomingTransactions.observe(viewLifecycleOwner, Observer {
            (transaction_list.adapter as TransactionAdapter).submitList(it)
        })

        val sharedPreferences: SharedPreferences = this.requireActivity().getSharedPreferences("OnboardingDetails", Context.MODE_PRIVATE)

        val monthlyBudget = sharedPreferences.getFloat("monthlyBudget",0F)
        var totalBalance = monthlyBudget*12
        net_balance.text = totalBalance.toString()
        Log.d("netbalance",totalBalance.toString())
        //the net balance (yearly) is calculated wrt the transactions already done
        viewModel.sumOfTransactions.observe(viewLifecycleOwner, Observer {
            if (it != null) {
                totalBalance += it
                net_balance.text = totalBalance.toString()
            }
        })
        val budgetPreferences: SharedPreferences =
                this.requireActivity().getSharedPreferences("Balance_details", Context.MODE_PRIVATE)
        val editor: SharedPreferences.Editor = budgetPreferences.edit()

        //setting pie chart initially to 0
        setPieChart(budgetPreferences,editor)
        //observing the cash details and the bank details to update the text view and the pie chart
        observeBalance(budgetPreferences,editor)
        //GraphCardView code
        //button for setting the balance details
        set_balance_details.setOnClickListener {
            setBalanceDetails(budgetPreferences,editor)
        }
    }
    //dialog box for setting the balance details
    private fun setBalanceDetails(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
        val dialog = LayoutInflater.from(requireContext()).inflate(
            R.layout.set_balance_details,
            null
        )
        //AlertDialogBuilder
        val mBuilder = AlertDialog.Builder(requireContext())
            .setView(dialog)
        //show dialog
        val  mAlertDialog = mBuilder.show()

        dialog.save_details.setOnClickListener {
            cashAmount = dialog.cash_amount.editText?.text.toString().toFloat()
            bankAmount = dialog.bank_amount.editText?.text.toString().toFloat()
            //saving the cashAmount and bankAmount to shared preferences for future use
            editor.putFloat("cashAmount", cashAmount).apply()
            editor.putFloat("bankAmount", bankAmount).apply()

            //setting the pie chart with new values
            setPieChart(budgetPreferences, editor)
            mAlertDialog.dismiss()
        }
        dialog.cancel_details.setOnClickListener { mAlertDialog.dismiss() }
        mAlertDialog.show()
    }

    private fun observeBalance(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {
        //getting the cashAmount and bankAmount and updating the views with live data

        var cashAmount = budgetPreferences.getFloat("cashAmount", 0F)
        var bankAmount = budgetPreferences.getFloat("bankAmount", 0F)
        viewModel.cashAmount.observe(viewLifecycleOwner, Observer {

            if (it != null) {
                cashAmount += it
                cash.text = "CASH : ${cashAmount}"
                Log.d("observeCash",cashAmount.toString())
                editor.putFloat("cashAmount",cashAmount).apply()//find solution to this
                setPieChart(budgetPreferences,editor)
            }
        })

        viewModel.bankAmount.observe(viewLifecycleOwner, Observer {
            if (it != null) {
                bankAmount+=it
                bank.text = "BANK : ${bankAmount}"
                Log.d("observeBank",bankAmount.toString())
                editor.putFloat("cashAmount",cashAmount).apply()
                setPieChart(budgetPreferences,editor)
            }
        })
        setPieChart(budgetPreferences,editor)
    }
    //https://www.geeksforgeeks.org/how-to-add-a-pie-chart-into-an-android-application/ use this for reference
    private fun setPieChart(budgetPreferences: SharedPreferences,editor: SharedPreferences.Editor) {

        val cashAmount = budgetPreferences.getFloat("cashAmount", 0f)
        val bankAmount = budgetPreferences.getFloat("bankAmount", 0f)
        Log.d("pieCank",cashAmount.toString())
        Log.d("pieBank",bankAmount.toString())
        cash.text = "CASH : ${cashAmount}"
        bank.text = "BANK : ${bankAmount}"

        val pieEntries = arrayListOf<PieEntry>()
        pieEntries.add(PieEntry(cashAmount))
        pieEntries.add(PieEntry(bankAmount))
        pieChart.animateXY(1000, 1000)

        // setup PieChart Entries Colors
        val pieDataSet = PieDataSet(pieEntries, "This is Pie Chart Label")
        pieDataSet.setColors(
            ContextCompat.getColor(requireActivity(), R.color.blue1),
            ContextCompat.getColor(requireActivity(), R.color.blue2)
        )
        val pieData = PieData(pieDataSet)

        // setip text in pieChart centre
        //piechart.setHoleColor(R.color.teal_700)
        pieChart.setHoleColor(getColorWithAlpha(Color.BLACK, 0.0f))
        // hide the piechart entries tags
        pieChart.legend.isEnabled = false
//        now hide the description of piechart
        pieChart.description.isEnabled = false
        pieChart.description.text = "Expanses"
        pieChart.holeRadius = 40f
        // this enabled the values on each pieEntry
        pieData.setDrawValues(true)
        pieChart.data = pieData

    }
    fun getColorWithAlpha(color: Int, ratio: Float): Int {
        var newColor = 0
        val alpha = Math.round(Color.alpha(color) * ratio)
        val r = Color.red(color)
        val g = Color.green(color)
        val b = Color.blue(color)
        newColor = Color.argb(alpha, r, g, b)
        return newColor
    }
}

正如应用程序重新启动时看到的那样,viewModel.cashAmount 会被触发,给出不想要的输出。我能做些什么来避免这种情况。

标签: kotlinandroid-fragmentsandroid-livedataandroid-lifecycleobserver-pattern

解决方案


活动可以重新创建很多,例如当您旋转屏幕时,或者如果它在后台并且系统将其销毁以释放一些内存。现在,每次发生这种情况时,您的代码都不知道它是获取当前值还是全新值,但其中一个应该执行计算,另一个应该只更新显示。

问题是您的计算逻辑与 UI 状态相关联——它被告知要显示什么,并决定这是否算作新的用户操作。它无法知道这一点。您的逻辑需要类似于

things observe LiveData values -> update to display new values when they come in
user clicks a button -> do calculation with the value they've entered
calculation result returns -> LiveData gets updated with new value
LiveData value changes -> things update to show the new value

这样,计算就会专门响应用户操作,例如通过单击按钮。LiveData观察者只反映当前状态,所以如果他们多次看到相同的值并不重要,他们只是重新绘制一个饼图或其他什么。


您可以使用 aLiveData来查看值流,但关于 UI 组件的问题是,有时它们会在那里看到它们,有时它们不会。并且LiveData专门用于将更新推送给活跃的观察者,而不是非活跃的观察者——并且总是向新的观察者提供最新的值,或者一个变得活跃的观察者。

因此,在这种情况下,它更像是“这是当前情况”,并且更适合显示事物,如果您重复自己也没关系。这就是为什么你不能在你的 UI 中做这种“一次处理所有事情”的事情——除非你真的在响应一个 UI 事件,比如按钮点击


推荐阅读