View not refreshed after first launching LiveData when using background work in Android

I am creating a debt app on Android using Dagger 2 , Room and MVVM . My problem is the reactivity of my main view where the list of debts is shown and you can mark them.

When this action is started, all debts are loaded correctly, however when I insert a new debt the view is not updated accordingly .

The weird part is that when I check one of them, the view refreshes as expected.

After a lot of debugging, I can conclude that the problem is with the lifecycle of the ViewModel, because debts are generated using a background job. If I hardcoded a new insert into the database in the ViewModel constructor, the view refreshes as expected.

Here is my code for Room Dao:

@Dao
interface DebtDao {

    @Insert(onConflict = OnConflictStrategy.ABORT)
    fun insert(debt: Debt)

    @Query("SELECT * FROM debts WHERE id=:debtId")
    fun findOne(debtId: Long): Debt

    @Query("SELECT * FROM debts")
    fun findAll(): LiveData<List<Debt>>

    @Query("""
        SELECT
            debts.*,
            p.name AS product_name,
            d.name AS debtor_name,
            c.name AS creditor_name
        FROM debts
            INNER JOIN products p ON debts.product_id = p.id
            INNER JOIN debtors d ON debts.debtor_id = d.id
            INNER JOIN creditors c ON debts.creditor_id = c.id
    """)
    fun findAllWithProductDebtorAndCreditor(): LiveData<List<DebtWithDebtorCreditorAndProduct>>

    @Update
    fun update(debt: Debt)

}

      

Activity:

class DebtsListActivity : AppCompatActivity() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val mainViewModel = ViewModelProviders.of(this, viewModelFactory).get(DebtsListViewModel::class.java)

        val debtsListAdapter = DebtsListAdapter(ArrayList()) {
            mainViewModel.payDebt(it.debt.id)
            showSuccess("Debt updated with success!")
        }

        mainViewModel.formattedDebts.observe(this, Observer<List<DebtWithDebtorCreditorAndProduct>> {
            if (it != null) {
               // This is only invoked when launching initially the activity or then ticking on of the debts as paid not when inserting a new debt                    
               debtsListAdapter.addItems(mainViewModel.getUnpaidDebts())
            }
        })

        rvDebts.layoutManager = LinearLayoutManager(this)
        rvDebts.adapter = debtsListAdapter
    }
}

      

View Model:

class DebtsListViewModel @Inject constructor(var debtDao: DebtDao) : ViewModel() {

    private var debts: LiveData<List<Debt>> = debtDao.findAll()
    var formattedDebts: LiveData<List<DebtWithDebtorCreditorAndProduct>> = Transformations.switchMap(debts) {
        debtDao.findAllWithProductDebtorAndCreditor()
    }

    fun payDebt(debtId: Long) {
        val paidDebt = debtDao.findOne(debtId)
        debtDao.update(paidDebt.copy(paid = true))
    }

    fun getUnpaidDebts(): List<DebtWithDebtorCreditorAndProduct> =
            formattedDebts.value?.filter { !it.debt.paid }.orEmpty()

}

      

What I would like to do is notify a formatted debt list containing all the required information.

Edit:

This is the background job code:

class GenerateDebtJobService : JobService() {

    @Inject
    lateinit var debtDao: DebtDao

    @Inject
    lateinit var productDao: ProductDao

    @Inject
    lateinit var productsDebtorsDao: ProductDebtorDao

    @Inject
    lateinit var productCreditorsDao: ProductCreditorDao

    override fun onStartJob(params: JobParameters): Boolean {
        DaggerGraphBuilder.build(applicationContext as FineApplication).inject(this)
        val productId = params.extras.getLong("id")
        val product = productDao.findOne(productId)
        val productCreditor = productCreditorsDao.findOneByProduct(productId)
        val debtors = productsDebtorsDao.findAllByProduct(productId)

        // When the bill day is reached for a given product the debtors list associated with that product is looped through and a new debt is created
        debtors.forEach {
            debtDao.insert(Debt(productId = it.productProductId, debtorId = it.productDebtorId, quantity = product.recurringCost, date = DateTime().toString(), creditorId = productCreditor.productCreditorId))
        }

        return false
    }

    override fun onStopJob(params: JobParameters): Boolean {
        Log.e("GenerateDebtJob", "job finished")
        return false
    }

    companion object {
        fun build(application: Application, productId: Long, periodicity: Days, startDay: Days) {
            val serviceComponent = ComponentName(application, GenerateDebtJobService::class.java)
            val bundle = PersistableBundle()
            bundle.putLong("id", productId)

            val builder = JobInfo.Builder(productId.toInt(), serviceComponent)
                    .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
//                    .setPeriodic(TimeUnit.DAYS.toMillis(periodicity.days.toLong()))
                    .setOverrideDeadline(1_000)
                    .setExtras(bundle)

            (application.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler).schedule(builder.build())
        }
    }

}

      

+1


source to share





All Articles