From 628b30ce5470e8beb562b0da5d063b7c848fccef Mon Sep 17 00:00:00 2001 From: Severiano Jaramillo Date: Sat, 8 Dec 2018 20:36:31 -0600 Subject: [PATCH] Created a method that is always listening to changes in the database and finds out when at least one UserAccount involved in a transaction is not present in the user_accounts db table. When such accounts are found they are retrieved in a form of a list and their information is requested to the BitShares nodes through graphenej's NetworkService and saved into the db once a response is received. The updated user account information is automatically displayed in the transactions list because of AAC's ViewModel, LiveData and Room. --- .../activities/ConnectedActivity.kt | 104 +++++++++++++----- .../adapters/TransfersDetailsAdapter.kt | 6 +- .../database/daos/UserAccountDao.kt | 10 +- .../database/joins/TransferDetail.kt | 4 +- .../database/joins/TransferDetailDao.kt | 2 +- .../processors/MissingAccountsLoader.kt | 42 ------- .../processors/TransfersLoader.kt | 26 +++-- .../repositories/UserAccountRepository.kt | 23 +++- .../bitsybitshareswallet/utils/Constants.kt | 11 +- .../viewmodels/UserAccountViewModel.kt | 8 ++ 10 files changed, 145 insertions(+), 91 deletions(-) delete mode 100644 app/src/main/java/cy/agorise/bitsybitshareswallet/processors/MissingAccountsLoader.kt diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt index 76f3564..1cc9049 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/activities/ConnectedActivity.kt @@ -10,9 +10,16 @@ import android.os.IBinder import android.util.Log import android.widget.Toast import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import cy.agorise.bitsybitshareswallet.utils.Constants +import cy.agorise.bitsybitshareswallet.viewmodels.UserAccountViewModel +import cy.agorise.graphenej.UserAccount import cy.agorise.graphenej.api.ConnectionStatusUpdate import cy.agorise.graphenej.api.android.NetworkService import cy.agorise.graphenej.api.android.RxBus +import cy.agorise.graphenej.api.calls.GetAccounts +import cy.agorise.graphenej.models.AccountProperties import cy.agorise.graphenej.models.FullAccountDetails import cy.agorise.graphenej.models.HistoryOperationDetail import cy.agorise.graphenej.models.JsonRpcResponse @@ -23,8 +30,9 @@ import io.reactivex.disposables.Disposable * Class in charge of managing the connection to graphenej's NetworkService */ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { + private val TAG = this.javaClass.simpleName - private val TAG = "ConnectedActivity" + private lateinit var mUserAccountViewModel: UserAccountViewModel private val mHandler = Handler() @@ -33,6 +41,8 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { private var storedOpCount: Long = -1 + private var missingUserAccounts = ArrayList() + /* Network service connection */ protected var mNetworkService: NetworkService? = null @@ -52,7 +62,10 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { handleJsonRpcResponse(message) // Payment detection focused responses if (message.error == null) { -// if (message.result is List<*> && (message.result as List<*>).size > 0) { + if (message.result is List<*> && (message.result as List<*>).size > 0) { + if ((message.result as List<*>)[0] is AccountProperties) { + handleAccountProperties(message.result as List) + } // if ((message.result as List<*>)[0] is FullAccountDetails) { // if (message.id == recurrentAccountUpdateId) { // handleAccountDetails((message.result as List<*>)[0] as FullAccountDetails) @@ -62,7 +75,7 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { // } // } else if (message.result is HistoryOperationDetail && message.id == accountOpRequestId) { // handleNewOperations(message.result as HistoryOperationDetail) -// } + } } else { // In case of error Log.e(TAG, "Got error message from full node. Msg: " + message.error.message) @@ -81,36 +94,47 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { } } } + + // Configure UserAccountViewModel to show the current account + mUserAccountViewModel = ViewModelProviders.of(this).get(UserAccountViewModel::class.java) + + mUserAccountViewModel.getMissingUserAccountIds().observe(this, Observer>{ userAccountIds -> + if (!userAccountIds.isEmpty()) { + for (userAccountId in userAccountIds) + missingUserAccounts.add(UserAccount(userAccountId)) + + mHandler.postDelayed(mRequestMissingUserAccountsTask, Constants.NETWORK_SERVICE_RETRY_PERIOD) + } + }) } - override fun onDestroy() { - super.onDestroy() - if (!mDisposable!!.isDisposed) mDisposable!!.dispose() - } + private fun handleAccountProperties(accountPropertiesList: List) { + val userAccounts = ArrayList() - override fun onResume() { - super.onResume() + for (accountProperties in accountPropertiesList) { + val userAccount = cy.agorise.bitsybitshareswallet.database.entities.UserAccount( + accountProperties.id, + accountProperties.name, + accountProperties.membership_expiration_date == Constants.LIFETIME_EXPIRATION_DATE + ) - val intent = Intent(this, NetworkService::class.java) - if (bindService(intent, this, Context.BIND_AUTO_CREATE)) { - mShouldUnbindNetwork = true - } else { - Log.e(TAG, "Binding to the network service failed.") + userAccounts.add(userAccount) } -// mHandler.postDelayed(mCheckMissingPaymentsTask, Constants.MISSING_PAYMENT_CHECK_PERIOD) -// storedOpCount = PreferenceManager.getDefaultSharedPreferences(this) -// .getLong(Constants.KEY_ACCOUNT_OPERATION_COUNT, -1) + mUserAccountViewModel.insertAll(userAccounts) } - override fun onPause() { - super.onPause() - // Unbinding from network service - if (mShouldUnbindNetwork) { - unbindService(this) - mShouldUnbindNetwork = false + /** + * Task used to obtain the missing UserAccounts. + */ + private val mRequestMissingUserAccountsTask = object : Runnable { + override fun run() { + if (mNetworkService!!.isConnected) { + mNetworkService!!.sendMessage(GetAccounts(missingUserAccounts), GetAccounts.REQUIRED_API) + } else { + mHandler.postDelayed(this, Constants.NETWORK_SERVICE_RETRY_PERIOD) + } } -// mHandler.removeCallbacks(mCheckMissingPaymentsTask) } /** @@ -146,7 +170,37 @@ abstract class ConnectedActivity : AppCompatActivity(), ServiceConnection { mNetworkService = binder.service } - override fun onServiceDisconnected(name: ComponentName?) { + override fun onServiceDisconnected(name: ComponentName?) { } + + override fun onPause() { + super.onPause() + // Unbinding from network service + if (mShouldUnbindNetwork) { + unbindService(this) + mShouldUnbindNetwork = false + } +// mHandler.removeCallbacks(mCheckMissingPaymentsTask) + mHandler.removeCallbacks(mRequestMissingUserAccountsTask) + } + + override fun onResume() { + super.onResume() + + val intent = Intent(this, NetworkService::class.java) + if (bindService(intent, this, Context.BIND_AUTO_CREATE)) { + mShouldUnbindNetwork = true + } else { + Log.e(TAG, "Binding to the network service failed.") + } +// mHandler.postDelayed(mCheckMissingPaymentsTask, Constants.MISSING_PAYMENT_CHECK_PERIOD) + +// storedOpCount = PreferenceManager.getDefaultSharedPreferences(this) +// .getLong(Constants.KEY_ACCOUNT_OPERATION_COUNT, -1) + } + + override fun onDestroy() { + super.onDestroy() + if (!mDisposable!!.isDisposed) mDisposable!!.dispose() } /** diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/adapters/TransfersDetailsAdapter.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/adapters/TransfersDetailsAdapter.kt index c82b82e..54ec790 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/adapters/TransfersDetailsAdapter.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/adapters/TransfersDetailsAdapter.kt @@ -88,8 +88,8 @@ class TransfersDetailsAdapter(private val context: Context) : if(transferDetail.direction) R.color.colorReceive else R.color.colorSend )) - viewHolder.tvFrom.text = transferDetail.from - viewHolder.tvTo.text = transferDetail.to + viewHolder.tvFrom.text = transferDetail.from ?: "" + viewHolder.tvTo.text = transferDetail.to ?: "" viewHolder.tvDate.text = "02 Oct" viewHolder.tvTime.text = "15:01:18 CET" @@ -104,7 +104,7 @@ class TransfersDetailsAdapter(private val context: Context) : Math.pow(10.toDouble(), transferDetail.cryptoPrecision.toDouble()) val cryptoAmount = "${df.format(amount)} ${transferDetail.cryptoSymbol}" viewHolder.tvCryptoAmount.text = cryptoAmount - + viewHolder.tvFiatEquivalent.text = "$4119.75" viewHolder.ivDirectionArrow.setImageDrawable(context.getDrawable( diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/UserAccountDao.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/UserAccountDao.kt index 9d77877..4705f15 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/UserAccountDao.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/daos/UserAccountDao.kt @@ -3,6 +3,7 @@ package cy.agorise.bitsybitshareswallet.database.daos import androidx.lifecycle.LiveData import androidx.room.Dao import androidx.room.Insert +import androidx.room.OnConflictStrategy import androidx.room.Query import cy.agorise.bitsybitshareswallet.database.entities.UserAccount @@ -11,9 +12,16 @@ interface UserAccountDao { @Insert fun insert(userAccount: UserAccount) + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun insertAll(userAccounts: List) + @Query("SELECT * FROM user_accounts WHERE user_accounts.id = :id") fun getUserAccount(id: String): LiveData @Query("SELECT * FROM user_accounts") - fun getAllUserAccounts(): LiveData> + fun getAll(): LiveData> + + // TODO not sure if this is the best place for this query as it involves two entities + @Query("SELECT DISTINCT destination FROM transfers WHERE destination NOT IN (SELECT id FROM user_accounts) UNION SELECT DISTINCT source FROM transfers WHERE source NOT IN (SELECT id FROM user_accounts)") + fun getMissingAccountIds(): LiveData> } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetail.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetail.kt index c7123f7..c2062f8 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetail.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetail.kt @@ -2,8 +2,8 @@ package cy.agorise.bitsybitshareswallet.database.joins data class TransferDetail( val id: String, - val from: String, - val to: String, + val from: String?, + val to: String?, val direction: Boolean, // True -> Received, False -> Sent // val date: Long, val cryptoAmount: Long, diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt index 45384e7..5945a61 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/database/joins/TransferDetailDao.kt @@ -7,6 +7,6 @@ import androidx.room.Query @Dao interface TransferDetailDao { - @Query("SELECT transfers.id, IFNULL((SELECT name FROM user_accounts WHERE user_accounts.id=transfers.source), '') AS `from`, IFNULL((SELECT name FROM user_accounts WHERE user_accounts.id=transfers.destination), '') AS `to`, (CASE WHEN destination=:userId THEN 1 ELSE 0 END) AS `direction`, transfers.transfer_amount AS `cryptoAmount`, assets.precision AS `cryptoPrecision`, assets.symbol AS cryptoSymbol FROM transfers INNER JOIN assets WHERE transfers.transfer_asset_id = assets.id") + @Query("SELECT transfers.id, (SELECT name FROM user_accounts WHERE user_accounts.id=transfers.source) AS `from`, (SELECT name FROM user_accounts WHERE user_accounts.id=transfers.destination) AS `to`, (CASE WHEN destination=:userId THEN 1 ELSE 0 END) AS `direction`, transfers.transfer_amount AS `cryptoAmount`, assets.precision AS `cryptoPrecision`, assets.symbol AS cryptoSymbol FROM transfers INNER JOIN assets WHERE transfers.transfer_asset_id = assets.id") fun getAll(userId: String): LiveData> } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/processors/MissingAccountsLoader.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/processors/MissingAccountsLoader.kt deleted file mode 100644 index 38d3593..0000000 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/processors/MissingAccountsLoader.kt +++ /dev/null @@ -1,42 +0,0 @@ -//package cy.agorise.bitsybitshareswallet.processors -// -//import android.content.Context -//import androidx.lifecycle.Lifecycle -//import cy.agorise.graphenej.api.android.RxBus -//import cy.agorise.graphenej.api.calls.GetAccounts -//import cy.agorise.graphenej.entities.AccountProperties -//import cy.agorise.graphenej.entities.JsonRpcResponse -//import io.reactivex.android.schedulers.AndroidSchedulers -// -///** -// * Loader used to fetch the missing accounts and update the database -// */ -//class MissingAccountsLoader(context: Context, lifecycle: Lifecycle) : BaseDataLoader(context, lifecycle) { -// -// init { -// mDisposable = RxBus.getBusInstance() -// .asFlowable() -// .observeOn(AndroidSchedulers.mainThread()) -// .subscribe { message -> -// if (message is JsonRpcResponse<*>) { -// if (message.result is List<*> && -// (message.result as List<*>).size > 0 && -// (message.result as List<*>)[0] is AccountProperties -// ) { -// database.putUserAccounts(message.result as List) -// } -// } -// } -// } -// -// protected fun onNetworkReady() { -// requestAccountInfo() -// } -// -// fun requestAccountInfo() { -// val missingAccountNames = database.getMissingAccountNames() -// if (missingAccountNames.size > 0) { -// mNetworkService.sendMessage(GetAccounts(missingAccountNames), GetAccounts.REQUIRED_API) -// } -// } -//} diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/processors/TransfersLoader.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/processors/TransfersLoader.kt index eb46b23..ce8e8c8 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/processors/TransfersLoader.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/processors/TransfersLoader.kt @@ -187,18 +187,21 @@ class TransfersLoader(private var mContext: Context?, private val mLifeCycle: Li // If we are in debug mode, we first erase all entries in the 'transfer' table transferRepository!!.deleteAll() } - mDisposables.add(transferRepository!!.getCount() - .subscribeOn(Schedulers.computation()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe { transferCount -> - if (transferCount > 0) { - // If we already have some transfers in the database, we might want to skip the request - // straight to the last batch - historicalTransferCount = Math.floor((transferCount / HISTORICAL_TRANSFER_BATCH_SIZE).toDouble()).toInt() + mDisposables.add( + transferRepository!!.getCount() + .subscribeOn(Schedulers.computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe { transferCount -> + if (transferCount > 0) { + // If we already have some transfers in the database, we might want to skip the request + // straight to the last batch + historicalTransferCount = Math.floor((transferCount / + HISTORICAL_TRANSFER_BATCH_SIZE).toDouble()).toInt() + } + // Retrieving account transactions + loadNextOperationsBatch() } - // Retrieving account transactions - loadNextOperationsBatch() - }) + ) } /** @@ -213,6 +216,7 @@ class TransfersLoader(private var mContext: Context?, private val mLifeCycle: Li historicalTransferCount++ val insertedCount = transferRepository!!.insertAll(processOperationList(operationHistoryList)) + // TODO return number of inserted rows // Log.d(TAG, String.format("Inserted count: %d, list size: %d", insertedCount, operationHistoryList.size)) if (/* insertedCount == 0 && */ operationHistoryList.isEmpty()) { // TODO Terminate process and obtain MissingTimes and MissingEquivalentValues diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/UserAccountRepository.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/UserAccountRepository.kt index b1a47b8..f6c1457 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/UserAccountRepository.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/repositories/UserAccountRepository.kt @@ -1,18 +1,18 @@ package cy.agorise.bitsybitshareswallet.repositories -import android.app.Application +import android.content.Context import android.os.AsyncTask import androidx.lifecycle.LiveData import cy.agorise.bitsybitshareswallet.database.BitsyDatabase import cy.agorise.bitsybitshareswallet.database.daos.UserAccountDao import cy.agorise.bitsybitshareswallet.database.entities.UserAccount -class UserAccountRepository internal constructor(application: Application) { +class UserAccountRepository internal constructor(context: Context) { private val mUserAccountDao: UserAccountDao init { - val db = BitsyDatabase.getDatabase(application) + val db = BitsyDatabase.getDatabase(context) mUserAccountDao = db!!.userAccountDao() } @@ -20,10 +20,18 @@ class UserAccountRepository internal constructor(application: Application) { insertAsyncTask(mUserAccountDao).execute(userAccount) } + fun insertAll(userAccounts: List) { + insertAllAsyncTask(mUserAccountDao).execute(userAccounts) + } + fun getUserAccount(id: String): LiveData { return mUserAccountDao.getUserAccount(id) } + fun getMissingUserAccountIds(): LiveData> { + return mUserAccountDao.getMissingAccountIds() + } + private class insertAsyncTask internal constructor(private val mAsyncTaskDao: UserAccountDao) : AsyncTask() { @@ -32,4 +40,13 @@ class UserAccountRepository internal constructor(application: Application) { return null } } + + private class insertAllAsyncTask internal constructor(private val mAsyncTaskDao: UserAccountDao) : + AsyncTask, Void, Void>() { + + override fun doInBackground(vararg userAccounts: List): Void? { + mAsyncTaskDao.insertAll(userAccounts[0]) + return null + } + } } \ No newline at end of file diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt index d8980f3..2ad4f26 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/utils/Constants.kt @@ -39,10 +39,15 @@ object Constants { const val LIFETIME_EXPIRATION_DATE = "1969-12-31T23:59:59" /** - * Smartcoin options for output + * Time period between two consecutive requests to the full node performed whenever we have + * open payment requests as a matter of redundancy. */ - val BTS = Asset("1.3.0") - val bitUSD = Asset("1.3.121") + const val MISSING_PAYMENT_CHECK_PERIOD: Long = 5000 + + /** + * Time period to wait to send a request to the NetworkService, and retry in case it is still not connected + */ + const val NETWORK_SERVICE_RETRY_PERIOD: Long = 5000 /** Key used to store the night mode setting into the shared preferences */ const val KEY_NIGHT_MODE_ACTIVATED = "key_night_mode_activated" diff --git a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/UserAccountViewModel.kt b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/UserAccountViewModel.kt index 1fdf87e..b8d433c 100644 --- a/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/UserAccountViewModel.kt +++ b/app/src/main/java/cy/agorise/bitsybitshareswallet/viewmodels/UserAccountViewModel.kt @@ -13,7 +13,15 @@ class UserAccountViewModel(application: Application) : AndroidViewModel(applicat return mRepository.getUserAccount(id) } + internal fun getMissingUserAccountIds(): LiveData> { + return mRepository.getMissingUserAccountIds() + } + // fun insert(userAccount: UserAccount) { // mRepository.insert(userAccount) // } + + fun insertAll(userAccounts: List) { + mRepository.insertAll(userAccounts) + } } \ No newline at end of file