Fix budget loading when returning from background

This commit is contained in:
William Brawner 2020-08-22 22:21:43 -07:00
parent cce6a3f371
commit 1b0afe3753
11 changed files with 88 additions and 67 deletions

View file

@ -66,9 +66,7 @@ dependencies {
implementation 'com.google.android.material:material:1.1.0'
implementation 'androidx.emoji:emoji-bundled:1.0.0'
implementation 'com.github.BlacKCaT27:CurrencyEditText:2.0.2'
def lifecycle_version = "1.1.1"
// ViewModel and LiveData
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
// Dagger
implementation "com.google.dagger:dagger:$dagger"
kapt "com.google.dagger:dagger-compiler:$dagger"

View file

@ -1,18 +1,13 @@
package com.wbrawner.budget.ui
import android.content.SharedPreferences
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.wbrawner.budget.common.budget.BudgetRepository
import com.wbrawner.budget.common.user.User
import com.wbrawner.budget.common.user.UserRepository
import com.wbrawner.budget.lib.repository.KEY_DEFAULT_BUDGET
import javax.inject.Inject
class SplashViewModel : ViewModel() {
@Inject
lateinit var sharedPreferences: SharedPreferences
@Inject
lateinit var budgetRepository: BudgetRepository
@ -43,15 +38,7 @@ class SplashViewModel : ViewModel() {
}
private suspend fun loadBudgetData() {
// TODO: This would fail for a new user that has no budgets
val budgets = budgetRepository.findAll()
val budgetId = sharedPreferences.getLong(KEY_DEFAULT_BUDGET, budgets.first().id!!)
budgetRepository.currentBudget = try {
budgetRepository.findById(budgetId)
} catch (e: Exception) {
// For some reason we can't find the default budget id, so fallback to the first budget
budgets.first()
}
budgetRepository.prefetchData()
}
}

View file

@ -23,7 +23,7 @@ class CategoryListFragment : ListWithAddButtonFragment<Category, CategoryListVie
override val noItemsStringRes: Int = R.string.categories_no_data
override val viewModel: CategoryListViewModel by viewModels()
override fun reloadItems() {
viewModel.getCategories()
viewModel.getCategories(viewLifecycleOwner)
}
override fun bindData(data: Category): BindableData<Category> = BindableData(data, CATEGORY_VIEW)

View file

@ -1,6 +1,8 @@
package com.wbrawner.budget.ui.categories
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import com.wbrawner.budget.AsyncState
import com.wbrawner.budget.AsyncViewModel
@ -19,15 +21,17 @@ class CategoryListViewModel : ViewModel(), AsyncViewModel<List<Category>> {
@Inject
lateinit var categoryRepo: CategoryRepository
fun getCategories() {
val budgetId = budgetRepo.currentBudget?.id
if (budgetId == null) {
state.postValue(AsyncState.Error("Invalid budget ID"))
return
}
launch {
categoryRepo.findAll(arrayOf(budgetId)).toList()
}
fun getCategories(lifecycleOwner: LifecycleOwner) {
budgetRepo.currentBudget.observe(lifecycleOwner, Observer {
val budgetId = budgetRepo.currentBudget.value?.id
if (budgetId == null) {
state.postValue(AsyncState.Error("Invalid budget ID"))
return@Observer
}
launch {
categoryRepo.findAll(arrayOf(budgetId)).toList()
}
})
}
suspend fun getBalance(category: Category): Long {

View file

@ -56,7 +56,7 @@ class OverviewFragment : Fragment() {
override fun onStart() {
super.onStart()
viewModel.loadOverview()
viewModel.loadOverview(viewLifecycleOwner)
}
companion object {

View file

@ -1,9 +1,7 @@
package com.wbrawner.budget.ui.overview
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.*
import com.wbrawner.budget.AsyncState
import com.wbrawner.budget.common.budget.Budget
import com.wbrawner.budget.common.budget.BudgetRepository
@ -20,33 +18,34 @@ class OverviewViewModel : ViewModel() {
@Inject
lateinit var transactionRepo: TransactionRepository
fun loadOverview() {
val budget = budgetRepo.currentBudget
if (budget == null) {
state.postValue(AsyncState.Error("Invalid Budget ID"))
return
}
viewModelScope.launch {
state.postValue(AsyncState.Loading)
try {
// TODO: Load expected and actual income/expense amounts as well
var balance = 0L
transactionRepo.findAll(listOf(budget.id!!)).forEach {
Log.d("OverviewViewModel", "${it.title} - ${it.amount}")
if (it.expense) {
balance -= it.amount
} else {
balance += it.amount
}
}
state.postValue(AsyncState.Success(OverviewState(
budget,
balance
)))
} catch (e: Exception) {
state.postValue(AsyncState.Error(e))
fun loadOverview(lifecycleOwner: LifecycleOwner) {
budgetRepo.currentBudget.observe(lifecycleOwner, Observer { budget ->
if (budget == null) {
state.postValue(AsyncState.Error("Invalid Budget ID"))
return@Observer
}
}
viewModelScope.launch {
state.postValue(AsyncState.Loading)
try {
// TODO: Load expected and actual income/expense amounts as well
var balance = 0L
transactionRepo.findAll(listOf(budget.id!!)).forEach {
Log.d("OverviewViewModel", "${it.title} - ${it.amount}")
if (it.expense) {
balance -= it.amount
} else {
balance += it.amount
}
}
state.postValue(AsyncState.Success(OverviewState(
budget,
balance
)))
} catch (e: Exception) {
state.postValue(AsyncState.Error(e))
}
}
})
}
}

View file

@ -20,7 +20,7 @@ class TransactionListViewModel : ViewModel(), AsyncViewModel<List<Transaction>>
override val state: MutableLiveData<AsyncState<List<Transaction>>> = MutableLiveData(AsyncState.Loading)
fun getTransactions(
budgetId: Long? = budgetRepository.currentBudget?.id,
budgetId: Long? = budgetRepository.currentBudget.value?.id,
categoryId: Long? = null,
start: Calendar? = null,
end: Calendar? = null

View file

@ -38,6 +38,7 @@ dependencies {
kapt "com.google.dagger:dagger-compiler:$rootProject.ext.dagger"
testImplementation 'junit:junit:4.12'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
}
repositories {
mavenCentral()

View file

@ -1,12 +1,15 @@
package com.wbrawner.budget.lib.repository
import android.content.SharedPreferences
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.wbrawner.budget.common.budget.Budget
import com.wbrawner.budget.common.budget.BudgetRepository
import com.wbrawner.budget.lib.network.BudgetApiService
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import java.util.concurrent.atomic.AtomicReference
const val KEY_DEFAULT_BUDGET = "defaultBudget"
@ -16,18 +19,38 @@ class NetworkBudgetRepository(
) : BudgetRepository {
private val mutex = Mutex()
private val budgets: MutableSet<Budget> = mutableSetOf()
private val currentBudgetRef = AtomicReference<Budget>()
override var currentBudget: Budget?
get() = currentBudgetRef.get()
set(value) {
currentBudgetRef.set(value)
override val currentBudget: LiveData<Budget?> = MutableLiveData()
init {
currentBudget.observeForever { budget ->
sharedPreferences.edit().apply {
value?.id?.let {
budget?.id?.let {
putLong(KEY_DEFAULT_BUDGET, it)
} ?: remove(KEY_DEFAULT_BUDGET)
apply()
}
}
GlobalScope.launch {
prefetchData()
}
}
override suspend fun prefetchData() {
val budgets = try {
findAll()
} catch (e: Exception) {
emptyList<Budget>()
}
if (budgets.isEmpty()) return
val budgetId = sharedPreferences.getLong(KEY_DEFAULT_BUDGET, budgets.first().id!!)
val budget = try {
findById(budgetId)
} catch (e: Exception) {
// For some reason we can't find the default budget id, so fallback to the first budget
budgets.first()
}
(currentBudget as MutableLiveData).postValue(budget)
}
override suspend fun create(newItem: Budget): Budget =
apiService.newBudget(NewBudgetRequest(newItem)).apply {
@ -44,12 +67,16 @@ class NetworkBudgetRepository(
}
}
override suspend fun findById(id: Long): Budget = mutex.withLock {
override suspend fun findById(id: Long, setCurrent: Boolean): Budget = (mutex.withLock {
budgets.firstOrNull { it.id == id }
} ?: apiService.getBudget(id).apply {
mutex.withLock {
budgets.add(this)
}
}).apply {
if (setCurrent) {
(currentBudget as MutableLiveData).postValue(this)
}
}
override suspend fun update(updatedItem: Budget): Budget =

View file

@ -5,6 +5,7 @@ buildscript {
ext.dagger = '2.23.1'
ext.hyperion = '0.9.27'
ext.kotlin_version = '1.3.72'
ext.lifecycle_version = "2.2.0"
ext.moshi = '1.8.0'
ext.retrofit = '2.6.0'
ext.room_version = "2.2.5"

View file

@ -1,8 +1,12 @@
package com.wbrawner.budget.common.budget
import androidx.lifecycle.LiveData
import com.wbrawner.budget.common.Repository
interface BudgetRepository : Repository<Budget, Long> {
var currentBudget: Budget?
val currentBudget: LiveData<Budget?>
override suspend fun findById(id: Long): Budget = findById(id, false)
suspend fun findById(id: Long, setCurrent: Boolean = false): Budget
suspend fun prefetchData()
suspend fun getBalance(id: Long): Long
}