Fixing wrong database entries & making the equivalent value currency dynamic
- Purging invalid entries of the 'equivalent_values' table - Displaying whathever currency the user happen to have selected *at the moment*. The previous implementation had a flaw that would have made the currency that was used to calculate the equivalent values in first stick, even if the user changed to another locale settings later.
This commit is contained in:
parent
1422ed699e
commit
8f70b3965d
10 changed files with 119 additions and 27 deletions
|
@ -4,6 +4,7 @@ import android.content.ComponentName
|
|||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.AsyncTask
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
|
@ -12,6 +13,7 @@ import android.preference.PreferenceManager
|
|||
import android.util.Log
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.pm.PackageInfoCompat
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import com.crashlytics.android.Crashlytics
|
||||
|
@ -47,6 +49,7 @@ import java.util.*
|
|||
import java.util.concurrent.TimeUnit
|
||||
import kotlin.collections.ArrayList
|
||||
import kotlin.collections.HashMap
|
||||
import kotlin.concurrent.thread
|
||||
|
||||
/**
|
||||
* The app uses the single Activity methodology, but this activity was created so that MainActivity can extend from it.
|
||||
|
@ -175,6 +178,20 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection {
|
|||
this.handleError(it)
|
||||
})
|
||||
mCompositeDisposable.add(disposable)
|
||||
|
||||
thread {
|
||||
val info = this.packageManager.getPackageInfo(this.packageName, PackageManager.GET_ACTIVITIES)
|
||||
val versionCode = PackageInfoCompat.getLongVersionCode(info)
|
||||
val hasPurgedEquivalentValues = PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.getBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, false)
|
||||
if(versionCode > 11 && !hasPurgedEquivalentValues) {
|
||||
mConnectedActivityViewModel.purgeEquivalentValues()
|
||||
PreferenceManager.getDefaultSharedPreferences(this)
|
||||
.edit()
|
||||
.putBoolean(Constants.KEY_HAS_PURGED_EQUIVALENT_VALUES, true)
|
||||
.apply()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -13,4 +13,10 @@ interface EquivalentValueDao {
|
|||
|
||||
@Query("SELECT * FROM equivalent_values")
|
||||
fun getAllEquivalentValues(): LiveData<List<EquivalentValue>>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM equivalent_values WHERE symbol=:currency")
|
||||
fun getEquivalentValuesCountForCurrency(currency: String): Long
|
||||
|
||||
@Query("DELETE FROM equivalent_values WHERE value < 0")
|
||||
fun purge(): Int
|
||||
}
|
|
@ -42,8 +42,8 @@ interface TransferDetailDao {
|
|||
assets.precision AS `assetPrecision`,
|
||||
assets.symbol AS `assetSymbol`,
|
||||
assets.issuer as `assetIssuer`,
|
||||
(SELECT value FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id) AS `fiatAmount`,
|
||||
(SELECT symbol FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id) AS `fiatSymbol`
|
||||
(SELECT value FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id AND symbol=:currency) AS `fiatAmount`,
|
||||
(SELECT symbol FROM equivalent_values WHERE equivalent_values.transfer_id=transfers.id AND symbol=:currency) AS `fiatSymbol`
|
||||
FROM
|
||||
transfers
|
||||
INNER JOIN
|
||||
|
@ -51,5 +51,5 @@ interface TransferDetailDao {
|
|||
WHERE
|
||||
transfers.transfer_asset_id = assets.id
|
||||
""")
|
||||
fun getAll(userId: String): LiveData<List<TransferDetail>>
|
||||
fun getAll(userId: String, currency: String): LiveData<List<TransferDetail>>
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
package cy.agorise.bitsybitshareswallet.network
|
||||
|
||||
import retrofit2.Call
|
||||
import cy.agorise.bitsybitshareswallet.models.coingecko.HistoricalPrice
|
||||
import retrofit2.Call
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Headers
|
||||
import retrofit2.http.Query
|
||||
|
@ -13,4 +13,8 @@ interface CoingeckoService {
|
|||
fun getHistoricalValueSync(@Query("id") id: String,
|
||||
@Query("date") date: String,
|
||||
@Query("localization") localization: Boolean): Call<HistoricalPrice>
|
||||
|
||||
@Headers("Content-Type: application/json")
|
||||
@GET("/api/v3/simple/supported_vs_currencies")
|
||||
fun getSupportedCurrencies(): Call<Array<String>>
|
||||
}
|
|
@ -15,4 +15,8 @@ class EquivalentValuesRepository(context: Context) {
|
|||
mEquivalentValuesDao = db?.equivalentValueDao()
|
||||
mTransfersDao = db?.transferDao()
|
||||
}
|
||||
|
||||
fun purge(): Int? {
|
||||
return mEquivalentValuesDao?.purge()
|
||||
}
|
||||
}
|
|
@ -3,24 +3,27 @@ package cy.agorise.bitsybitshareswallet.repositories
|
|||
import android.content.Context
|
||||
import androidx.lifecycle.LiveData
|
||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||
import cy.agorise.bitsybitshareswallet.database.daos.EquivalentValueDao
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetailDao
|
||||
|
||||
class TransferDetailRepository internal constructor(context: Context) {
|
||||
|
||||
private val mTransferDetailDao: TransferDetailDao
|
||||
private val mEquivalentValuesDao: EquivalentValueDao
|
||||
|
||||
init {
|
||||
val db = BitsyDatabase.getDatabase(context)
|
||||
mTransferDetailDao = db!!.transferDetailDao()
|
||||
mEquivalentValuesDao = db.equivalentValueDao()
|
||||
}
|
||||
|
||||
fun get(userId: String, transferId: String): LiveData<TransferDetail> {
|
||||
return mTransferDetailDao.get(userId, transferId)
|
||||
}
|
||||
|
||||
fun getAll(userId: String): LiveData<List<TransferDetail>> {
|
||||
return mTransferDetailDao.getAll(userId)
|
||||
fun getAll(userId: String, currency: String): LiveData<List<TransferDetail>> {
|
||||
return mTransferDetailDao.getAll(userId, currency)
|
||||
}
|
||||
|
||||
}
|
|
@ -12,9 +12,9 @@ import cy.agorise.bitsybitshareswallet.database.entities.Transfer
|
|||
import cy.agorise.bitsybitshareswallet.network.CoingeckoService
|
||||
import cy.agorise.bitsybitshareswallet.network.ServiceGenerator
|
||||
import cy.agorise.bitsybitshareswallet.utils.Constants
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
|
@ -77,18 +77,16 @@ class TransferRepository internal constructor(context: Context) {
|
|||
*/
|
||||
fun observeMissingEquivalentValuesIn(symbol: String) {
|
||||
compositeDisposable.add(mTransferDao.getTransfersWithMissingValueIn(symbol)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(Schedulers.io())
|
||||
.map { transfer -> obtainFiatValue(transfer, symbol) }
|
||||
.subscribe({
|
||||
Log.d(TAG,"Got equivalent value: $it")
|
||||
if(it.value >= 0) mEquivalentValuesDao.insert(it)
|
||||
},{
|
||||
Log.e(TAG,"Error while trying to create a new equivalent value. Msg: ${it.message}")
|
||||
for(element in it.stackTrace){
|
||||
Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}")
|
||||
}
|
||||
}))
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -110,13 +108,7 @@ class TransferRepository internal constructor(context: Context) {
|
|||
?.execute()
|
||||
var equivalentFiatValue = -1L
|
||||
if(response?.isSuccessful == true){
|
||||
// We try to use the locale-selected currency first
|
||||
var selectedCurrency = symbol
|
||||
// Checking that the provided currency is supported by the Coingecko API
|
||||
val isCurrencySupported = response?.body()?.market_data?.current_price?.keys?.contains(symbol.toLowerCase())
|
||||
// In case it is not, we fallback to USD
|
||||
if(isCurrencySupported == false) selectedCurrency = "usd"
|
||||
val price: Double = response.body()?.market_data?.current_price?.get(selectedCurrency.toLowerCase()) ?: -1.0
|
||||
val price: Double = response.body()?.market_data?.current_price?.get(symbol.toLowerCase()) ?: -1.0
|
||||
if(price > 0){
|
||||
// The equivalent value is obtained by:
|
||||
// 1- Dividing the base value by 100000 (BTS native precision)
|
||||
|
@ -168,4 +160,27 @@ class TransferRepository internal constructor(context: Context) {
|
|||
if(!compositeDisposable.isDisposed)
|
||||
compositeDisposable.clear()
|
||||
}
|
||||
|
||||
/**
|
||||
* Method used to override a given currency if it turns out not to be supported by the API.
|
||||
* <p>
|
||||
* The CoinGecko API supports 20+ fiat currencies. So we can very easily calculate historical
|
||||
* equivalent values for those currencies. If the currency is not supported though,
|
||||
* we must fall back to USD.
|
||||
*
|
||||
* @param symbol The 3 letters symbol of the currency
|
||||
*/
|
||||
fun getSupportedCurrency(symbol: String): Observable<String> {
|
||||
return Observable.just(symbol)
|
||||
.map {
|
||||
val sg = ServiceGenerator(Constants.COINGECKO_URL)
|
||||
val response = sg.getService(CoingeckoService::class.java)
|
||||
?.getSupportedCurrencies()
|
||||
?.execute()
|
||||
if(response?.body()?.indexOf(symbol.toLowerCase()) == -1)
|
||||
"usd"
|
||||
else
|
||||
it
|
||||
}
|
||||
}
|
||||
}
|
|
@ -128,7 +128,10 @@ object Constants {
|
|||
/** Minimum time period in seconds between BitShares nodes list updates */
|
||||
const val NODES_UPDATE_PERIOD = (60 * 60).toLong() // 1 hour
|
||||
|
||||
|
||||
/** Because of a bug in pre-version code 11 releases, some entries in the equivalent values
|
||||
* table were invalid. We'll be performing a purge at version code 12, but we only want to do
|
||||
* it once. After this, we record this in shared preferences using this key */
|
||||
const val KEY_HAS_PURGED_EQUIVALENT_VALUES = "key_has_purged_equivalent_values"
|
||||
/////////////////////// Crashlytics custom keys ///////////////////////
|
||||
|
||||
/** Key used to add the last visited fragment name to the Crashlytics report */
|
||||
|
|
|
@ -1,15 +1,21 @@
|
|||
package cy.agorise.bitsybitshareswallet.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import cy.agorise.bitsybitshareswallet.database.BitsyDatabase
|
||||
import cy.agorise.bitsybitshareswallet.repositories.EquivalentValuesRepository
|
||||
import cy.agorise.bitsybitshareswallet.repositories.NodeRepository
|
||||
import cy.agorise.bitsybitshareswallet.repositories.TransferRepository
|
||||
import cy.agorise.graphenej.network.FullNode
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
class ConnectedActivityViewModel(application: Application) : AndroidViewModel(application) {
|
||||
companion object {
|
||||
val TAG = "ConnectedActivityVM"
|
||||
}
|
||||
private var mTransfersRepository = TransferRepository(application)
|
||||
|
||||
private var mEquivalentValuesRep = EquivalentValuesRepository(application)
|
||||
private val mNodeRepository: NodeRepository
|
||||
|
||||
init {
|
||||
|
@ -18,7 +24,17 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap
|
|||
}
|
||||
|
||||
fun observeMissingEquivalentValuesIn(symbol: String) {
|
||||
mTransfersRepository.observeMissingEquivalentValuesIn(symbol)
|
||||
mTransfersRepository.getSupportedCurrency(symbol)
|
||||
.observeOn(Schedulers.io())
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe({
|
||||
currency -> mTransfersRepository.observeMissingEquivalentValuesIn(currency)
|
||||
},{
|
||||
Log.e(TAG,"Error while trying to subscribe to missing equivalent values observer. Msg: ${it.message}")
|
||||
for(element in it.stackTrace){
|
||||
Log.e(TAG,"${element.className}#${element.methodName}:${element.lineNumber}")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun updateNodeLatencies(nodes: List<FullNode>) {
|
||||
|
@ -29,4 +45,8 @@ class ConnectedActivityViewModel(application: Application) : AndroidViewModel(ap
|
|||
super.onCleared()
|
||||
mTransfersRepository.onCleared()
|
||||
}
|
||||
|
||||
fun purgeEquivalentValues(): Int? {
|
||||
return mEquivalentValuesRep.purge()
|
||||
}
|
||||
}
|
|
@ -1,17 +1,25 @@
|
|||
package cy.agorise.bitsybitshareswallet.viewmodels
|
||||
|
||||
import android.app.Application
|
||||
import android.util.Log
|
||||
import androidx.lifecycle.*
|
||||
import cy.agorise.bitsybitshareswallet.database.joins.TransferDetail
|
||||
import cy.agorise.bitsybitshareswallet.models.FilterOptions
|
||||
import cy.agorise.bitsybitshareswallet.repositories.TransferDetailRepository
|
||||
import cy.agorise.bitsybitshareswallet.repositories.TransferRepository
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
class TransactionsViewModel(application: Application) : AndroidViewModel(application) {
|
||||
companion object {
|
||||
val TAG = "TransactionsViewModel"
|
||||
}
|
||||
private var mRepository = TransferDetailRepository(application)
|
||||
private var mTransfersRepository = TransferRepository(application)
|
||||
|
||||
/**
|
||||
* [FilterOptions] used to filter the list of [TransferDetail] taken from the database
|
||||
|
@ -35,13 +43,25 @@ class TransactionsViewModel(application: Application) : AndroidViewModel(applica
|
|||
}
|
||||
|
||||
internal fun getFilteredTransactions(userId: String): LiveData<List<TransferDetail>> {
|
||||
transactions = mRepository.getAll(userId)
|
||||
val currency = Currency.getInstance(Locale.getDefault())
|
||||
mTransfersRepository.getSupportedCurrency(currency.currencyCode)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({
|
||||
Log.d(TAG,"Looking for currency with code: ${it}")
|
||||
transactions = mRepository.getAll(userId, it)
|
||||
|
||||
filteredTransactions.addSource(transactions) { transactions ->
|
||||
viewModelScope.launch {
|
||||
filteredTransactions.value = filter(transactions, mFilterOptions)
|
||||
}
|
||||
}
|
||||
},{
|
||||
Log.e(TAG,"Error while trying to obtain a filtered list of transactions. Msg: ${it.message}")
|
||||
for(element in it.stackTrace){
|
||||
Log.e(ConnectedActivityViewModel.TAG,"${element.className}#${element.methodName}:${element.lineNumber}")
|
||||
}
|
||||
})
|
||||
|
||||
return filteredTransactions
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue