#Update ROOM Database using Jetpack Compose - kotlin

I'm am making a note app using kotlin jetpack compose. Currently, I'm working on an update feature. I'm trying to update a note in ROOM Database but it doesn't update. I already read and follow some tutorials from documentation and youtube, however, I still haven't solved it.
Here is my code:
Data
#RequiresApi(Build.VERSION_CODES.O)
#Entity(tableName = "KeepNotesTable")
data class NoteData (
#PrimaryKey
val id: String = UUID.randomUUID().toString(),
#ColumnInfo(name = "noteTitle")
var title: String,
#ColumnInfo(name = "noteDescription")
var description: String,
#ColumnInfo(name = "noteDate")
val date: Date = Date.from(Instant.now())
)
DAO
#Update(onConflict = OnConflictStrategy.REPLACE)
suspend fun updateNote(note: NoteData)
Repository
suspend fun updateNote(note: NoteData) = noteDatabaseDao.updateNote(note)
ViewModel
fun updateNote(note: NoteData) = viewModelScope.launch { repository.updateNote(note) }
Navigation
#RequiresApi(Build.VERSION_CODES.O)
#Composable
fun ScreenNavigation(noteViewModel: NoteViewModel = viewModel()) {
val noteList = noteViewModel.noteList.collectAsState().value
val navController = rememberNavController()
NavHost(navController = navController, startDestination = Screen.MainScreen.name) {
composable(Screen.MainScreen.name){
MainScreen(navController = navController, noteList = noteList, removeNote = { noteViewModel.removeNote(it) })}
composable(Screen.AddNoteScreen.name){
AddNoteScreen(navController = navController, addNote = { noteViewModel.addNote(it) })
}
composable(Screen.UpdateNoteScreen.name + "/{noteId}",
arguments = listOf(navArgument(name = "noteId") {type = NavType.StringType})
) { backStackEntry ->
UpdateNoteScreen(
noteList = noteList,
navController = navController,
updateNote = { noteViewModel.updateNote(it) },
NoteId = backStackEntry.arguments?.getString("noteId")
)
}
}
}
UpdateNoteScreen
#RequiresApi(Build.VERSION_CODES.O)
#Composable
fun UpdateNoteScreen(
noteList: List<NoteData>,
navController: NavController,
updateNote: (NoteData) -> Unit,
NoteId: String?,
) {
val fetchNote = noteList.filter { note ->
note.id == NoteId
}
val note = fetchNote.first()
var noteTitle = note.title
var noteDescription = note.description
var newTitle = ""
var newDescription = ""
var title by remember {
mutableStateOf(noteTitle + newTitle)
}
var description by remember {
mutableStateOf(noteDescription + newDescription)
}
val context = LocalContext.current
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colors.primaryVariant) {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally) {
Card(
contentColor = MaterialTheme.colors.primary,
backgroundColor = MaterialTheme.colors.background,
elevation = 8.dp,
modifier = Modifier
.fillMaxWidth()
.padding(start = 10.dp, end = 10.dp, top = 20.dp, bottom = 20.dp)) {
Column(
horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier
.fillMaxWidth()
.padding(start = 15.dp, end = 15.dp, top = 10.dp, bottom = 40.dp)) {
InputText(
modifier = Modifier.fillMaxWidth(),
text = title,
label = "",
onTextChange = {
newTitle = it
title = newTitle
}
)
Spacer(modifier = Modifier.height(10.dp))
InputText(
modifier = Modifier
.fillMaxWidth()
.fillMaxHeight(0.5f),
text = description,
label = "",
maxLine = 1000,
imeAction = ImeAction.None,
onTextChange = {
newDescription = it
description = newDescription
}
)
}
}
Row() {
DiscardButton(text = "Cancel", onClick = {
navController.navigate(route = Screen.MainScreen.name)
})
Spacer(modifier = Modifier.width(10.dp))
SaveButton(text = "Update", onClick = {
if (title.isNotEmpty() && description.isNotEmpty()) {
updateNote(NoteData(title = title, description = description))
navController.navigate(route = Screen.MainScreen.name)
Toast.makeText(context, "Successfully Updated", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(context, "Fill the Title and Description", Toast.LENGTH_SHORT).show()
}
})
}
}
}
}

I reviewed my code again and noticed that an id parameter is needed to pass in the updateNote() to find and update the specific note. Therefore,
updateNoteScreen
updateNote(NoteData(title = title, description = description)
should be
updateNote(NoteData(id = note.id, title = title, description = description)

Related

How to implement Compose Navigation Arguments

I'm practicing Jetpack compose navigation, currently I'm stuck at passing arguments, so the correct information can be displayed when clicked.
I'm trying to navigate from this Destination, MenuScreen;
#Composable
fun HomeScreen(onHomeCardClick: () -> Unit) {
HomeContentScreen(onHomeCardClick = onHomeCardClick)
}
#Composable
fun HomeContentScreen(
modifier: Modifier = Modifier,
onHomeCardClick: () -> Unit
) {
Column(
modifier
.verticalScroll(rememberScrollState())
.padding(vertical = 16.dp)) {
HomeQuote()
....
}
}
To this destination, MenuInfoScreen;
#Composable
fun HomeInfoScreen(){
WelcomeText()
HomeInfoDetails()
}
#Composable
fun WelcomeText() {
Text(
text = "Welcome, to Home Information",
style = MaterialTheme.typography.h3,
modifier = Modifier.padding(horizontal = 12.dp, vertical = 18.dp)
)
}
#Composable
fun HomeInfoDetails(
homeInfo: HomeInfo
) {
Card(
modifier = Modifier
.padding(10.dp)
.clip(CircleShape),
elevation = 10.dp,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(10.dp)
) {
Image(
painter = painterResource(id = homeInfo.homeInfoImageId),
contentDescription = null,
modifier = Modifier
.fillMaxWidth()
.clip(shape = RoundedCornerShape(topEnd = 4.dp, bottomEnd = 4.dp)),
contentScale = ContentScale.Fit
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = homeInfo.title,
style = MaterialTheme.typography.h3
)
Spacer(modifier = Modifier.height(16.dp))
Text(
text = homeInfo.description,
style = MaterialTheme.typography.h5
)
}
}
}
Here's the model I'm trying to follow, HomeInfoModel;
object HomeInfoModel {
val homeInfoModelList = listOf(
HomeInfo(
id = 1,
title = "Monty",
sex = "Male",
age = 14,
description = "Monty enjoys chicken treats and cuddling while watching Seinfeld.",
homeInfoImageId = R.drawable.ab1_inversions
),
.....
)
}
Here's the my NavHost;
NavHost(
navController = navController,
startDestination = Home.route,
modifier = modifier
) {
// builder parameter will be defined here as the graph
composable(route = Home.route) {
HomeScreen(
onHomeCardClick = {}
)
}
....
composable(route = HomeInfoDestination.route) {
HomeInfoScreen()
}
}
}
And my Destinations file;
object Home : KetoDestination {
override val route = "home"
}
....
object HomeInfoDestination : KetoDestination{
override val route = "homeInfo"
}
I know this is a lot and I'm a bit off, but please I've been stuck here for more than a week now. Any little information willl surely come handy.
And Of Course very much appreciated.
Thanks for your help in advance.
You should extract the arguments from the NavBackStackEntry that is available in the lambda of the composable() function.
You can read more in the official android docs https://developer.android.com/jetpack/compose/navigation#nav-with-args

How to dynamically create menu in jetpack compose

I'm wanting the menu to load the data that comes from the database. This is being static manually, does anyone have any idea how I can do this? Here's my current structure. Anything if you need more code details I can show you
My method:
#Composable
private fun DrawerContent(
scope: CoroutineScope,
scaffoldState:ScaffoldState,
navController: NavController
){
val items = listOf(
Screen.Entry1,
Screen.Entry2,
Screen.Entry3
)
Column(modifier= Modifier
.background(colorResource(id = R.color.pastel_green))
.fillMaxWidth()
.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally) {
Text(
"Menu", style = MaterialTheme.typography.h5,
modifier = Modifier.padding(bottom = 20.dp,top=20.dp),
color = colorResource(id = R.color.marron)
)
val current by navController.currentBackStackEntryAsState()
val currentRoute = current?.destination
items.forEach { screen ->
var selected = currentRoute?.hierarchy?.any { it.route == screen.route } == true
val selectedColor = if (selected) colorResource(id = R.color.cinza) else Color.Transparent
val colorfont = if (selected) colorResource(id = R.color.white) else colorResource(id = R.color.cinza)
Row(modifier = Modifier
.fillMaxWidth()
.height(32.dp)
.background(selectedColor)
.clickable {
selected =
currentRoute?.hierarchy?.any { it.route == screen.route } == true
scope.launch { scaffoldState.drawerState.close() }
navController.navigate(screen.route) {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}) {
Text(stringResource(screen.resourceString),fontSize = 20.sp,
color= colorfont)
}
}
}
}
Class Screen:
sealed class Screen(val route: String, #StringRes val resourceString: Int,#IdRes val resourceId:Int) {
object Entry1 : Screen("entry1", R.string.entry1, R.id.entry1)
object Entry2 : Screen("entry2", R.string.entry2, R.id.entry2)
object Entry3 : Screen("entry3", R.string.entry3, R.id.entry3)
}
solved
My Code:
NavHost(
navController = navController,
// 1st change: Set startDestination to the exact string of route
startDestination = "entry${items[0].category_id}/{$ARG_ID}", // NOT "dynamic/1", provide arguments via defaultValue
) {
items.forEach { screen ->
composable(
route = "entry${screen.category_id}/{$ARG_ID}",
// 2nd change: Set startDestination argument via defaultValue
arguments = listOf(navArgument(ARG_ID) {
type = NavType.IntType; defaultValue =
screen.category_id
}),
) {
entryAbstract.Entry1(id = it.arguments?.getInt(ARG_ID))
}
}
}
companion object
{
const val ARG_ID = "id"
}
My method composable
#Composable
private fun DrawerContent(scope: CoroutineScope,scaffoldState:ScaffoldState
,navController: NavController
) {
Column(
modifier = Modifier
.background(colorResource(id = R.color.pastel_green))
.fillMaxWidth()
.fillMaxHeight(),
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
"Menu", style = MaterialTheme.typography.h5,
modifier = Modifier.padding(bottom = 20.dp, top = 20.dp),
color = colorResource(id = R.color.marron)
)
val current by navController.currentBackStackEntryAsState()
val currentRoute = current?.destination
items.forEach { screen ->
var selected = currentRoute?.hierarchy?.any { it.route == "entry${screen.category_id}/{$ARG_ID}" } == true
val selectedColor = if (selected) colorResource(id = R.color.cinza) else Color.Transparent
val colorfont = if (selected) colorResource(id = R.color.white) else colorResource(id = R.color.cinza)
Row(modifier = Modifier
.fillMaxWidth()
.height(32.dp)
.background(selectedColor)
.clickable {
selected =
currentRoute?.hierarchy?.any { it.route == "entry${screen.category_id}/{$ARG_ID}" } == true
scope.launch { scaffoldState.drawerState.close() }
navController.navigate("entry${screen.category_id}/${screen.category_id}") {
// Pop up to the start destination of the graph to
// avoid building up a large stack of destinations
// on the back stack as users select items
popUpTo(navController.graph.findStartDestination().id) {
saveState = true
}
// Avoid multiple copies of the same destination when
// reselecting the same item
launchSingleTop = true
// Restore state when reselecting a previously selected item
restoreState = true
}
}) {
Text(screen.category_name, fontSize = 20.sp, color = colorfont)
}
}
}
}

Observing viewmodel's liveData as state doesn't trigger a recomposition of the composable

A colleague of mine has written the following code:
Viewmodel:
class CallMeBackViewModel(
private val ewayControllerUseCase: EwayControllerUseCase,
private val accountControllerUseCase: AccountControllerUseCase
) : ViewModel() {
val accountDetails = MutableLiveData<AccountDetailsResponse>()
val contactForm = CallMeBackForm()
init {
val accountId = App.instance.appContext.getPreferenceThroughKeystore(PreferenceKeys.ACCOUNT_ID.key)
contactForm.timeframe = "09:00 - 11:00"
if (accountId != "null") {
getAccountDetails(accountId)
}
}
private fun getAccountDetails(accountId: String) {
viewModelScope.launch(Dispatchers.IO) {
when (val response = accountControllerUseCase.getAccountDetails(accountId)) {
is ResponseResult.Success -> {
accountDetails.postValue(response.data!!)
fillContactFormByAccount(response.data!!)
}
is ResponseResult.Error -> {
Log.d("GET_ACCOUNT_DETAILS_ERROR", "Network Call Failed With Exception: " + response.exception.message)
}
}
}
}
private fun fillContactFormByAccount(accountDetailsResponse: AccountDetailsResponse) {
contactForm.firstName = accountDetailsResponse.firstName!!
contactForm.lastName = accountDetailsResponse.lastName!!
contactForm.phoneNumber = accountDetailsResponse.phone1!!
contactForm.accountId = accountDetailsResponse.accountId!!
}
#ExperimentalFoundationApi
#ExperimentalMaterialApi
fun submitCallbackForm(activity: MainActivity) {
viewModelScope.launch(Dispatchers.IO) {
when (val response = ewayControllerUseCase.submitCallbackForm(contactForm)) {
is ResponseResult.Success -> {
onSuccessfulSubmission(activity)
}
is ResponseResult.Error -> {
Log.d("CALLBACK_FORM_SUBMISSION_ERROR", "Network Call Failed With Exception: " + response.exception.message)
}
}
}
}
#ExperimentalFoundationApi
#ExperimentalMaterialApi
private suspend fun onSuccessfulSubmission(activity: MainActivity) {
withContext(Dispatchers.Main) {
activity.onBackPressed()
}
}
}
He basically fetches some form data and when received, updates the accountDetails live data with them. Now in the composable screen he observes this field as a state like so :
Screen:
#ExperimentalFoundationApi
#ExperimentalMaterialApi
#Composable
fun ContactCallMeBack() {
val viewModel: CallMeBackViewModel = remember { getKoin().get() }
val accountDetails by viewModel.accountDetails.observeAsState(null)
val canProceed = remember { mutableStateOf(false) }
val textChangedToggle = remember { mutableStateOf(false) }
val activity = LocalContext.current as MainActivity
LaunchedEffect(textChangedToggle.value) {
canProceed.value = canProceed(viewModel.contactForm)
}
Column(modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 16.dp)
.verticalScroll(rememberScrollState())
) {
TopBarPadding(true)
Spacer(modifier = Modifier.padding(24.dp))
if (accountDetails != null) {
Form(accountDetails, viewModel, textChangedToggle)
}
else {
Form(accountDetails, viewModel, textChangedToggle)
}
Spacer(modifier = Modifier.padding(42.fixedDp()))
MyButton(text = "Καλέστε με",
buttonType = if (canProceed.value) {
MyButtonType.PRIMARY
} else {
MyButtonType.DISABLED
},
onClick = { viewModel.submitCallbackForm(activity) })
Spacer(modifier = Modifier.padding(49.fixedDp()))
}
}
#Composable
private fun Form(
accountDetails: AccountDetailsResponse?,
viewModel: CallMeBackViewModel,
textChangedToggle: MutableState<Boolean>
) {
Column(modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(13.dp))
.background(Color.White)
.padding(start = 16.dp, end = 16.dp, top = 22.dp, bottom = 24.dp)
) {
InputField(label = "Κωδικός Συνδρομητή",
initialValue = accountDetails?.accountId ?: "",
onTextChangeCallback = {
viewModel.contactForm.accountId = it
triggerCheck(textChangedToggle)
}
)
Spacer(modifier = Modifier.padding(18.fixedDp()))
InputField(label = "Όνομα",
initialValue = accountDetails?.firstName ?: "",
onTextChangeCallback = {
viewModel.contactForm.firstName = it
triggerCheck(textChangedToggle)
}
)
Spacer(modifier = Modifier.padding(18.fixedDp()))
InputField(label = "Επώνυμο",
initialValue = accountDetails?.lastName ?: "",
onTextChangeCallback = {
viewModel.contactForm.lastName = it
triggerCheck(textChangedToggle)
}
)
Spacer(modifier = Modifier.padding(18.fixedDp()))
InputField(label = "Τηλέφωνο",
initialValue = accountDetails?.phone1 ?: "",
onTextChangeCallback = {
viewModel.contactForm.phoneNumber = it
triggerCheck(textChangedToggle)
}
)
Spacer(modifier = Modifier.padding(18.fixedDp()))
MyDropdown(label = "Διαθεσιμότητα", list = mapOf(
1 to "09:00 - 11:00",
2 to "11:00 - 13:00",
3 to "13:00 - 15:00",
4 to "15:00 - 17:00"
),
defaultSelected = 1,
disableSelectionReset = true,
onSelectionChangeCallbackValue = { viewModel.contactForm.timeframe = it }
)
Spacer(modifier = Modifier.padding(18.fixedDp()))
InputField(label = "Μήνυμα", singleLine = false,
onTextChangeCallback = {
viewModel.contactForm.reason = it
triggerCheck(textChangedToggle)
}
)
}
}
private fun canProceed(form: CallMeBackForm): Boolean {
return (form.firstName != "" &&
form.lastName != "" &&
form.phoneNumber != "" &&
form.reason != "")
}
private fun triggerCheck(state: MutableState<Boolean>) {
state.value = !state.value
}
InputField composable:
#Composable
fun InputField(
label: String,
enabled: Boolean = true,
isPassword: Boolean = false,
infoButton: (() -> Unit)? = null,
placeholder: String = LocalContext.current.getString(R.string.input_placeholder),
canReveal: Boolean = false,
phonePrefix: Boolean = false,
singleLine: Boolean = true,
optional: Boolean = false,
initialValue: String = "",
onTextChangeCallback: (String) -> Unit = { },
numbersOnly: Boolean = false
) {
Column {
Row(
verticalAlignment = Alignment.CenterVertically,
modifier = Modifier.padding(bottom = 10.fixedDp())
) {
Text(
text = label,
fontSize = 16.sp,
lineHeight = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(end = 10.fixedDp())
)
if (optional) {
Text(
text = LocalContext.current.getString(R.string.optional),
fontSize = 16.sp,
lineHeight = 20.sp,
fontWeight = FontWeight.Bold,
modifier = Modifier.padding(end = 10.fixedDp()),
color = colorResource(id = R.color.input_field_optional_text)
)
}
if (infoButton != null) {
InfoButton(
buttonColor = colorResource(id = R.color.Orange_100),
onClick = infoButton
)
}
}
Spacer(modifier = Modifier.padding(10.fixedDp()))
val textFieldValue = remember { mutableStateOf(TextFieldValue(initialValue)) }
val textFieldPasswordMode = remember { mutableStateOf(isPassword) }
val modifier = if (singleLine) {
Modifier
.clip(RoundedCornerShape(6.dp))
.fillMaxWidth()
.background(if (enabled) Color.White else colorResource(id = R.color.Grey_10))
} else {
Modifier
.clip(RoundedCornerShape(6.dp))
.fillMaxWidth()
.requiredHeight(100.dp)
.background(if (enabled) Color.White else colorResource(id = R.color.Grey_10))
}
var isTextFieldFocused by remember { mutableStateOf(false) }
ShadowWrapper(
cardElevation = 1.dp,
border = BorderStroke(
1.dp,
if (isTextFieldFocused && enabled) colorResource(id = R.color.Orange_100)
else colorResource(id = R.color.my_card_border)
),
shadowShapeRadius = 6.dp,
shadowElevation = 1.dp
) {
TextField(
value = textFieldValue.value,
onValueChange = {
textFieldValue.value = it
onTextChangeCallback.invoke(it.text)
},
modifier = modifier
.onFocusChanged { isTextFieldFocused = it.hasFocus },
keyboardOptions = if (numbersOnly) {
KeyboardOptions(keyboardType = KeyboardType.Phone)
} else {
KeyboardOptions(keyboardType = KeyboardType.Text)
},
enabled = enabled,
colors = TextFieldDefaults.textFieldColors(
disabledTextColor = colorResource(id = R.color.Grey_60),
focusedIndicatorColor = Color.Transparent,
backgroundColor = if (enabled) colorResource(id = R.color.white) else colorResource(
id = R.color.Grey_10
),
textColor = colorResource(id = R.color.black),
unfocusedIndicatorColor = colorResource(id = R.color.white),
disabledIndicatorColor = colorResource(id = R.color.white),
cursorColor = colorResource(id = R.color.black)
),
singleLine = singleLine,
placeholder = {
Text(
text = placeholder,
fontStyle = FontStyle.Italic,
fontSize = 16.sp,
lineHeight = 20.sp,
overflow = TextOverflow.Visible
)
},
readOnly = !enabled,
visualTransformation = if (textFieldPasswordMode.value) PasswordVisualTransformation() else VisualTransformation.None,
leadingIcon = if (phonePrefix) {
{
PhonePrefix(R.drawable.greek_flag, "+30")
}
} else null,
trailingIcon = if (canReveal) {
{
RevealingEye(state = textFieldPasswordMode)
}
} else null,
shape = RoundedCornerShape(6.dp),
)
}
}
}
I noticed the seemingly useless if statement around the form, but it seems to be the only thing forcing the recomposition of the Form composable. I thought that when a new accountDetails instance is posted inside an observable state, that this would trigger any composables that depend on it to recompose. What am I missing?

How can select one out of 4 composabel in compose?

Suppose I have four Button or Composable in a row, I want to select one and deselect the other, not like the radio button but behave exactly like that.
#Composable
fun ButtonSet(modifier: Modifier = Modifier) {
Row(
modifier = modifier
.padding(start = 60.dp, end = 60.dp)
.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
) {
val button1 = remember { mutableStateOf(false) }
val button2 = remember { mutableStateOf(false) }
val button3 = remember { mutableStateOf(false) }
val button4 = remember { mutableStateOf(false) }
val buttonList = listOf<Any>(button1, button2, button3, button4)
val activeButton = remember {
mutableStateOf(true)
}
if (button1.value || button2.value || button3.value || button4.value) {
activeButton.value = false
} else if (button1.value) {
button1.value = !button1.value
} else if (button2.value) {
button2.value = !button2.value
} else if (button3.value) {
button3.value = !button3.value
} else if (button4.value) {
button4.value = !button4.value
}
GoalkeeperButton(button1.value)
GoalkeeperButton(button2.value)
GoalkeeperButton(button3.value)
GoalkeeperButton(button4.value)
}
}
#Composable
fun GoalkeeperButton(
active: Boolean = false,
#SuppressLint("ModifierParameter") modifier: Modifier = Modifier,
) {
val buttonState = remember {
mutableStateOf(active)
}
if (!buttonState.value) {
ButtonUnActive() {}
} else {
ButtonActive()
}
RadioButton(selected =, onClick = { /*TODO*/ })
}
#Composable
fun ButtonActive(active: Boolean = true) {
val button = painterResource(id = R.drawable.bttn_pressed)
val gloves = painterResource(id = R.drawable.blue_hands)
val buttonState = remember {
mutableStateOf(active)
}
Column(
modifier = Modifier
.wrapContentWidth()
.height(130.dp)
) {
Image(
painter = gloves,
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
.size(width = 135.dp, height = 70.dp)
)
Image(
painter = button,
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
.clickable(onClick = {})
.size(width = 107.dp, height = 65.dp)
)
}
}
#Composable
fun ButtonUnActive(
state: Boolean = false,
onclick: () -> Unit,
) {
val button = painterResource(id = R.drawable.bttn)
var unActiveState by remember {
mutableStateOf(state)
}
Image(
painter = button,
contentDescription = null,
contentScale = ContentScale.FillBounds,
modifier = Modifier
.clickable(
onClick = {
onclick()
},
)
.size(width = 107.dp, height = 65.dp)
)
}
This is what I tried, but I know it's very bad code, please suggest a better and best way.

Integrating Braintree dropin UI into Kotlin Jetpack Compose

I'm trying to convert the android/java dropin UI code from here https://developer.paypal.com/braintree/docs/guides/drop-in/setup-and-integration#starting-drop-in into a jetpack compose app. So far I have
#Composable
fun Account(user: FinUser) {
val context = LocalContext.current
val customerToken = user.userData["customerToken"] as String
val dropInRequest = DropInRequest()
.clientToken(customerToken)
val dropInHintLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartIntentSenderForResult()
) {
print("pause here")
}
val dropInIntent = dropInRequest.getIntent(context)
val dropInPendingIntent = PendingIntent.getBroadcast(
context, 200, dropInIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
Column(){
Column(
Modifier
.padding(top = 0.dp)
.clickable { launchDropInUi(
dropInHintLauncher=dropInHintLauncher,
dropInPendingIntent=dropInPendingIntent) }) {
Divider(color = Color.LightGray, thickness = 1.dp)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp, 10.dp)
) {
Column() {
Text("Payment", color = Color.Gray)
Text("*********9999", color = Color.Black)
}
Spacer(modifier = Modifier.fillMaxWidth())
}
Divider(color = Color.LightGray, thickness = 1.dp)
}
}
}
fun launchDropInUi(dropInHintLauncher: ManagedActivityResultLauncher<IntentSenderRequest, ActivityResult>, dropInPendingIntent: PendingIntent){
dropInHintLauncher.launch(
IntentSenderRequest.Builder(dropInPendingIntent).build()
)
}
When I click on my row there's no dropin UI popup but it does register the click and runs over the launchDropInUi function.
I found the issue. I needed to use
val dropInHintLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
)
Giving
#Composable
fun Account(user: FinUser) {
val context = LocalContext.current
val customerToken = user.userData["customerToken"] as String
val dropInRequest = DropInRequest()
.clientToken(customerToken)
val dropInHintLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.StartActivityForResult()
){ result: ActivityResult ->
if (result.resultCode == Activity.RESULT_OK) {
// you will get result here in result.data
val data: DropInResult? = result.data?.getParcelableExtra(DropInResult.EXTRA_DROP_IN_RESULT)
}else{
print("throw error popup")
}
}
val dropInIntent = dropInRequest.getIntent(context)
Column(){
Column(
Modifier
.padding(top = 0.dp)
.clickable { launchDropInUi(
dropInHintLauncher =dropInHintLauncher,
dropInIntent =dropInIntent) }) {
Divider(color = Color.LightGray, thickness = 1.dp)
Row(
modifier = Modifier
.fillMaxWidth()
.padding(20.dp, 10.dp)
) {
Column() {
Text("Payment", color = Color.Gray)
Text("*********9999", color = Color.Black)
}
Spacer(modifier = Modifier.fillMaxWidth())
}
Divider(color = Color.LightGray, thickness = 1.dp)
}
}
}
fun launchDropInUi(dropInHintLauncher: ManagedActivityResultLauncher<Intent, ActivityResult>, dropInIntent: Intent){
dropInHintLauncher.launch(dropInIntent)
}