Skip to content

Commit

Permalink
[Android] Improve performance when listing big credentials (#41)
Browse files Browse the repository at this point in the history
This PR adds a CredentialPackViewModel to solve performance issues with large credentials.
  • Loading branch information
Juliano1612 authored Nov 8, 2024
1 parent cd8a5c2 commit 52d6546
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 87 deletions.
13 changes: 7 additions & 6 deletions example/src/main/java/com/spruceid/mobilesdkexample/HomeView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,13 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.spruceid.mobilesdkexample.ui.theme.Bg
import com.spruceid.mobilesdkexample.ui.theme.Inter
import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
import com.spruceid.mobilesdkexample.verifier.VerifierHomeView
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.wallet.WalletHomeView

Expand All @@ -41,7 +38,8 @@ enum class HomeTabs {
@Composable
fun HomeView(
navController: NavController,
verificationMethodsViewModel: VerificationMethodsViewModel
verificationMethodsViewModel: VerificationMethodsViewModel,
credentialPacksViewModel: CredentialPacksViewModel
) {
var tab by remember {
mutableStateOf(HomeTabs.WALLET)
Expand Down Expand Up @@ -92,7 +90,10 @@ fun HomeView(
) {
Box(modifier = Modifier.padding(bottom = 30.dp)) {
if (tab == HomeTabs.WALLET) {
WalletHomeView(navController)
WalletHomeView(
navController,
credentialPacksViewModel = credentialPacksViewModel
)
} else {
VerifierHomeView(
navController = navController,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.navigation.SetupNavGraph
import com.spruceid.mobilesdkexample.ui.theme.Bg
import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModelFactory
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModelFactory

Expand Down Expand Up @@ -69,7 +71,15 @@ class MainActivity : ComponentActivity() {
VerificationMethodsViewModelFactory((application as MainApplication).verificationMethodsRepository)
}

SetupNavGraph(navController, verificationMethodsViewModel = verificationMethodsViewModel)
val credentialPacksViewModel: CredentialPacksViewModel by viewModels {
CredentialPacksViewModelFactory(application as MainApplication)
}

SetupNavGraph(
navController,
verificationMethodsViewModel = verificationMethodsViewModel,
credentialPacksViewModel = credentialPacksViewModel
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.spruceid.mobilesdkexample.credentials

import StorageManager
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
Expand All @@ -19,35 +18,32 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavHostController
import androidx.navigation.compose.rememberNavController
import com.spruceid.mobile.sdk.CredentialPack
import com.spruceid.mobilesdkexample.ErrorView
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.ui.theme.CTAButtonGreen
import com.spruceid.mobilesdkexample.ui.theme.Inter
import com.spruceid.mobilesdkexample.ui.theme.MobileSdkTheme
import com.spruceid.mobilesdkexample.ui.theme.SecondaryButtonRed
import com.spruceid.mobilesdkexample.ui.theme.TextHeader
import com.spruceid.mobilesdkexample.utils.credentialDisplaySelector
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import kotlinx.coroutines.launch

@Composable
fun AddToWalletView(
navController: NavHostController,
rawCredential: String,
credentialPacksViewModel: CredentialPacksViewModel
) {
var credentialItem by remember { mutableStateOf<ICredentialView?>(null) }
var err by remember { mutableStateOf<String?>(null) }

val scope = rememberCoroutineScope()
val context = LocalContext.current

LaunchedEffect(Unit) {
credentialItem = credentialDisplaySelector(rawCredential, null)
Expand All @@ -64,7 +60,7 @@ fun AddToWalletView(
val credentialPack = CredentialPack()
try {
credentialPack.tryAddRawCredential(rawCredential)
credentialPack.save(StorageManager(context = context))
credentialPacksViewModel.saveCredentialPack(credentialPack)
back()
} catch (e: Exception) {
err = e.localizedMessage
Expand Down Expand Up @@ -152,16 +148,3 @@ fun AddToWalletView(
}

}

@Preview(showBackground = true)
@Composable
fun AddToWalletPreview() {
val navController: NavHostController = rememberNavController()

MobileSdkTheme {
AddToWalletView(
navController = navController,
rawCredential = "{}",
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.spruceid.mobilesdkexample.verifier.VerifyEAView
import com.spruceid.mobilesdkexample.verifier.VerifyMDocView
import com.spruceid.mobilesdkexample.verifier.VerifyVCView
import com.spruceid.mobilesdkexample.verifiersettings.VerifierSettingsHomeView
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import com.spruceid.mobilesdkexample.viewmodels.VerificationMethodsViewModel
import com.spruceid.mobilesdkexample.wallet.DispatchQRView
import com.spruceid.mobilesdkexample.wallet.HandleOID4VCIView
Expand All @@ -23,15 +24,17 @@ import com.spruceid.mobilesdkexample.walletsettings.WalletSettingsHomeView
@Composable
fun SetupNavGraph(
navController: NavHostController,
verificationMethodsViewModel: VerificationMethodsViewModel
verificationMethodsViewModel: VerificationMethodsViewModel,
credentialPacksViewModel: CredentialPacksViewModel
) {
NavHost(navController = navController, startDestination = Screen.HomeScreen.route) {
composable(
route = Screen.HomeScreen.route,
) {
HomeView(
navController,
verificationMethodsViewModel = verificationMethodsViewModel
verificationMethodsViewModel = verificationMethodsViewModel,
credentialPacksViewModel = credentialPacksViewModel
)
}
composable(
Expand Down Expand Up @@ -74,14 +77,14 @@ fun SetupNavGraph(
}
composable(
route = Screen.WalletSettingsHomeScreen.route,
) { WalletSettingsHomeView(navController) }
) { WalletSettingsHomeView(navController, credentialPacksViewModel) }
composable(
route = Screen.AddToWalletScreen.route,
deepLinks =
listOf(navDeepLink { uriPattern = "spruceid://?sd-jwt={rawCredential}" })
) { backStackEntry ->
val rawCredential = backStackEntry.arguments?.getString("rawCredential")!!
AddToWalletView(navController, rawCredential)
AddToWalletView(navController, rawCredential, credentialPacksViewModel)
}
composable(
route = Screen.ScanQRScreen.route,
Expand All @@ -90,7 +93,7 @@ fun SetupNavGraph(
route = Screen.HandleOID4VCI.route,
) { backStackEntry ->
val url = backStackEntry.arguments?.getString("url")!!
HandleOID4VCIView(navController, url)
HandleOID4VCIView(navController, url, credentialPacksViewModel)
}
composable(
route = Screen.HandleOID4VP.route,
Expand All @@ -100,7 +103,7 @@ fun SetupNavGraph(
if (!url.startsWith("openid4vp")) {
url = "openid4vp://$url"
}
HandleOID4VPView(navController, url)
HandleOID4VPView(navController, url, credentialPacksViewModel)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package com.spruceid.mobilesdkexample.viewmodels

import StorageManager
import android.app.Application
import android.content.Context
import androidx.compose.runtime.toMutableStateList
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import com.spruceid.mobile.sdk.CredentialPack
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class CredentialPacksViewModel(application: Application) : AndroidViewModel(application) {
private val storageManager = StorageManager(context = (application as Context))
private val _credentialPacks = MutableStateFlow(listOf<CredentialPack>())
val credentialPacks = _credentialPacks.asStateFlow()

init {
viewModelScope.launch {
_credentialPacks.value = CredentialPack.loadPacks(storageManager)
}
}

fun saveCredentialPack(credentialPack: CredentialPack) {
credentialPack.save(storageManager)
val tmpCredentialPacksList = _credentialPacks.value.toMutableStateList()
tmpCredentialPacksList.add(credentialPack)
_credentialPacks.value = tmpCredentialPacksList
}

fun deleteAllCredentialPacks() {
_credentialPacks.value.forEach { credentialPack ->
credentialPack.remove(storageManager)
}
_credentialPacks.value = emptyList()
}

fun deleteCredentialPack(credentialPack: CredentialPack) {
credentialPack.remove(storageManager)
val tmpCredentialPacksList = _credentialPacks.value.toMutableStateList()
tmpCredentialPacksList.remove(credentialPack)
_credentialPacks.value = tmpCredentialPacksList
}
}

class CredentialPacksViewModelFactory(private val application: Application) :
ViewModelProvider.NewInstanceFactory() {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T =
CredentialPacksViewModel(application) as T
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,20 @@ import com.spruceid.mobilesdkexample.LoadingView
import com.spruceid.mobilesdkexample.R
import com.spruceid.mobilesdkexample.credentials.AddToWalletView
import com.spruceid.mobilesdkexample.navigation.Screen
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import io.ktor.client.HttpClient
import io.ktor.client.engine.cio.CIO
import io.ktor.client.request.request
import io.ktor.client.request.setBody
import io.ktor.client.statement.readBytes
import io.ktor.http.HttpMethod
import io.ktor.util.toMap
import org.json.JSONObject

@Composable
fun HandleOID4VCIView(
navController: NavHostController,
url: String
url: String,
credentialPacksViewModel: CredentialPacksViewModel
) {
var loading by remember {
mutableStateOf(false)
Expand Down Expand Up @@ -123,11 +124,7 @@ fun HandleOID4VCIView(

credentials?.forEach { cred ->
cred.payload.toString(Charsets.UTF_8).let {
// Removes the renderMethod to avoid storage issues
// TODO: Optimize credential decrypt and display
val json = JSONObject(it)
json.remove("renderMethod")
credential = json.toString()
credential = it
}
}
} catch (e: Exception) {
Expand All @@ -153,6 +150,7 @@ fun HandleOID4VCIView(
AddToWalletView(
navController = navController,
rawCredential = credential!!,
credentialPacksViewModel = credentialPacksViewModel
)
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.spruceid.mobilesdkexample.wallet

import StorageManager
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
Expand Down Expand Up @@ -31,7 +30,6 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.ParagraphStyle
Expand All @@ -44,7 +42,6 @@ import androidx.compose.ui.text.withStyle
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import com.spruceid.mobile.sdk.CredentialPack
import com.spruceid.mobile.sdk.rs.Holder
import com.spruceid.mobile.sdk.rs.ParsedCredential
import com.spruceid.mobile.sdk.rs.PermissionRequest
Expand All @@ -65,6 +62,7 @@ import com.spruceid.mobilesdkexample.ui.theme.Inter
import com.spruceid.mobilesdkexample.ui.theme.TextBase
import com.spruceid.mobilesdkexample.ui.theme.TextHeader
import com.spruceid.mobilesdkexample.utils.trustedDids
import com.spruceid.mobilesdkexample.viewmodels.CredentialPacksViewModel
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand All @@ -73,15 +71,11 @@ import org.json.JSONObject
@Composable
fun HandleOID4VPView(
navController: NavController,
url: String
url: String,
credentialPacksViewModel: CredentialPacksViewModel
) {
val scope = rememberCoroutineScope()

val context = LocalContext.current
val storageManager = StorageManager(context = context)
val credentialPacks = remember {
mutableStateOf(CredentialPack.loadPacks(storageManager))
}
val credentialPacks = credentialPacksViewModel.credentialPacks

var credentialClaims by remember { mutableStateOf(mapOf<String, JSONObject>()) }
var holder by remember { mutableStateOf<Holder?>(null) }
Expand Down
Loading

0 comments on commit 52d6546

Please sign in to comment.