I have problem about call flow under FlowResponder.
Scenario explain
As you can see I need to call subflow for insert/update just only for NodeB.
Can I do that?
Here when I try I got this error
java.io.IOException: Payload invalid
at net.corda.node.services.statemachine.FlowStateMachineImplKt.checkPayloadIs(FlowStateMachineImpl.kt:605) ~[corda-node-3.3-corda.jar:?]
at net.corda.node.services.statemachine.FlowStateMachineImpl.receive(FlowStateMachineImpl.kt:231) ~[corda-node-3.3-corda.jar:?]
at net.corda.node.services.statemachine.FlowSessionImpl.receive(FlowSessionImpl.kt:44) ~[corda-node-3.3-corda.jar:?]
at net.corda.node.services.statemachine.FlowSessionImpl.receive(FlowSessionImpl.kt:48) ~[corda-node-3.3-corda.jar:?]
at net.corda.core.flows.ReceiveTransactionFlow.call(ReceiveTransactionFlow.kt:74) ~[corda-core-3.3-corda.jar:?]
at net.corda.core.flows.ReceiveTransactionFlow.call(ReceiveTransactionFlow.kt:24) ~[corda-core-3.3-corda.jar:?]
at net.corda.core.flows.FlowLogic.subFlow(FlowLogic.kt:290) ~[corda-core-3.3-corda.jar:?]
at net.corda.core.flows.SignTransactionFlow.call(CollectSignaturesFlow.kt:212) ~[corda-core-3.3-corda.jar:?]
at net.corda.core.flows.SignTransactionFlow.call(CollectSignaturesFlow.kt:197) ~[corda-core-3.3-corda.jar:?]
at net.corda.core.flows.FlowLogic.subFlow(FlowLogic.kt:290) ~[corda-core-3.3-corda.jar:?]
at th.co.jventures.ddlp.cordapp.flows.CustomerIssueFlowResponder.call(CustomerIssueFlow.kt:196) ~[classes/:?]
at th.co.jventures.ddlp.cordapp.flows.CustomerIssueFlowResponder.call(CustomerIssueFlow.kt:177) ~[classes/:?]
at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:96) [corda-node-3.3-corda.jar:?]
at net.corda.node.services.statemachine.FlowStateMachineImpl.run(FlowStateMachineImpl.kt:44) [corda-node-3.3-corda.jar:?]
at co.paralleluniverse.fibers.Fiber.run1(Fiber.java:1092) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at co.paralleluniverse.fibers.Fiber.exec(Fiber.java:788) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at co.paralleluniverse.fibers.RunnableFiberTask.doExec(RunnableFiberTask.java:100) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at co.paralleluniverse.fibers.RunnableFiberTask.run(RunnableFiberTask.java:91) [quasar-core-0.7.9-jdk8.jar:0.7.9]
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [?:1.8.0_181]
at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266) [?:1.8.0_181]
at java.util.concurrent.FutureTask.run(FutureTask.java) [?:1.8.0_181]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:180) [?:1.8.0_181]
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:293) [?:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_181]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_181]
at net.corda.node.utilities.AffinityExecutor$ServiceAffinityExecutor$1$thread$1.run(AffinityExecutor.kt:62) [corda-node-3.3-corda.jar:?]
Caused by: java.io.NotSerializableException: Described type with descriptor net.corda:Rd6hxg+0oJROxKDXK8OerA== was expected to be of type class net.corda.core.transactions.SignedTransaction but was java.util.List
at net.corda.nodeapi.internal.serialization.amqp.DeserializationInput.readObject$node_api(DeserializationInput.kt:133) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.amqp.DeserializationInput.readObjectOrNull$node_api(DeserializationInput.kt:109) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.amqp.DeserializationInput.readObjectOrNull$node_api$default(DeserializationInput.kt:108) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.amqp.DeserializationInput$deserialize$1.invoke(DeserializationInput.kt:98) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.amqp.DeserializationInput.des(DeserializationInput.kt:80) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.amqp.DeserializationInput.deserialize(DeserializationInput.kt:96) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.amqp.AbstractAMQPSerializationScheme.deserialize(AMQPSerializationScheme.kt:123) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.SerializationFactoryImpl$deserialize$1$1.invoke(SerializationScheme.kt:111) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.core.serialization.SerializationFactory.withCurrentContext(SerializationAPI.kt:66) ~[corda-core-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.SerializationFactoryImpl$deserialize$1.invoke(SerializationScheme.kt:111) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.SerializationFactoryImpl$deserialize$1.invoke(SerializationScheme.kt:86) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.core.serialization.SerializationFactory.asCurrent(SerializationAPI.kt:80) ~[corda-core-3.3-corda.jar:?]
at net.corda.nodeapi.internal.serialization.SerializationFactoryImpl.deserialize(SerializationScheme.kt:111) ~[corda-node-api-3.3-corda.jar:?]
at net.corda.node.services.statemachine.FlowStateMachineImplKt.checkPayloadIs(FlowStateMachineImpl.kt:603) ~[corda-node-3.3-corda.jar:?]
Here initiate flow code
package th.co.jventures.ddlp.cordapp.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.confidential.IdentitySyncFlow
import net.corda.core.contracts.Command
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.requireThat
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.unwrap
import th.co.jventures.ddlp.cordapp.contracts.CustomerContract
import th.co.jventures.ddlp.cordapp.core.commons.CordappConstants
import th.co.jventures.ddlp.cordapp.dto.CustomerIssueData
import th.co.jventures.ddlp.cordapp.services.CustomerService
import th.co.jventures.ddlp.cordapp.states.Address
import th.co.jventures.ddlp.cordapp.states.Consent
import th.co.jventures.ddlp.cordapp.states.CustomerState
import th.co.jventures.ddlp.cordapp.utils.CordappValidationUtils.Companion.requiredNotWhen
import java.util.*
#InitiatingFlow
#StartableByRPC
class CustomerIssueFlow(private val customerIssueData: CustomerIssueData) : FlowLogic<String>() {
companion object {
object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on new IOU.")
object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.")
object SIGNING_TRANSACTION : ProgressTracker.Step("Signing transaction with our private key.")
object GATHERING_SIGS : ProgressTracker.Step("Gathering the counterparty's signature.") {
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
}
object FINALISING_TRANSACTION : ProgressTracker.Step("Obtaining notary signature and recording transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
GENERATING_TRANSACTION,
VERIFYING_TRANSACTION,
SIGNING_TRANSACTION,
GATHERING_SIGS,
FINALISING_TRANSACTION
)
}
override val progressTracker = tracker()
#Suspendable
override fun call(): String {
// Obtain a reference to the notary we want to use.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// Stage 1.
progressTracker.currentStep = GENERATING_TRANSACTION
// Generate an unsigned transaction.
val customerService = serviceHub.cordaService(CustomerService::class.java)
//#################################################################//
val pahNode = serviceHub.networkMapCache.getPeerByLegalName(CordappConstants.Node.PAH) as Party
val participants = listOf(ourIdentity, pahNode)
val customerStateAndRef = customerService.findByIdCardNoAndPartner(idCarNo = customerIssueData.customerData.idCardNo!!, partner = customerIssueData.customerData.partner!!)
//check duplicate customer
requiredNotWhen(customerStateAndRef == null, "Customer already registered")
val addresses = customerIssueData.customerData.addresses?.map { it ->
Address(
seqId = it.seqId,
id = UUID.randomUUID().toString(),
type = it.type,
houseNumber = it.houseNumber,
street = it.street,
subDistrict = it.subDistrict,
district = it.district,
province = it.province,
postalCode = it.postalCode,
countryCode = it.countryCode,
mobile = it.mobile,
telephone = it.telephone
)
}
val consentsArray = customerIssueData.customerData.consents?.map { it ->
Consent(
id = UUID.randomUUID().toString(),
partner = it.partner,
product = it.product,
reference = it.reference,
createDate = it.createDate
)
}
val linearId = UniqueIdentifier()
val customerState = CustomerState(
linearId = linearId,
externalCustomerNo = customerIssueData.customerData.externalCustomerNo!!,
title = customerIssueData.customerData.title!!,
firstName = customerIssueData.customerData.firstName!!,
middleName = customerIssueData.customerData.middleName!!,
lastName = customerIssueData.customerData.lastName!!,
idCardNo = customerIssueData.customerData.idCardNo!!,
email = customerIssueData.customerData.email!!,
partner = customerIssueData.customerData.partner!!,
product = customerIssueData.customerData.product!!,
consents = consentsArray,
status = CordappConstants.CustomerStatus.Active,
addresses = addresses,
participants = participants,
createDate = Date(),
createBy = customerIssueData.customerData.username!!,
changeDate = Date(),
changeBy = customerIssueData.customerData.username!!
)
val txCommand = Command(CustomerContract.Commands.Issue(), customerState.participants.map { it.owningKey })
val txBuilder = TransactionBuilder(notary)
.addOutputState(customerState, CustomerContract.CONTRACT_ID)
.addCommand(txCommand)
//#################################################################//
// Stage 2.
progressTracker.currentStep = VERIFYING_TRANSACTION
// Verify that the transaction is valid.
txBuilder.verify(serviceHub)
// Stage 3.
progressTracker.currentStep = SIGNING_TRANSACTION
// Sign the transaction.
val partSignedTx = serviceHub.signInitialTransaction(txBuilder)
// Stage 4.
progressTracker.currentStep = GATHERING_SIGS
// Send the state to the counterparty, and receive it back with their signature.
val session = (customerState.participants - ourIdentity).map { initiateFlow(it) }
val fullySignedTx = subFlow(CollectSignaturesFlow(partSignedTx, session, GATHERING_SIGS.childProgressTracker()))
// Stage 5.
progressTracker.currentStep = FINALISING_TRANSACTION
// Notarise and record the transaction in both parties' vaults.
subFlow(FinalityFlow(fullySignedTx, FINALISING_TRANSACTION.childProgressTracker()))
return customerState.linearId.toString()
}
}
#InitiatedBy(CustomerIssueFlow::class)
class CustomerIssueFlowResponder(val otherSideSession: FlowSession) : FlowLogic<SignedTransaction>() {// TwoPartyDealFlow.Acceptor(otherSideSession) {
#Suspendable
override fun call(): SignedTransaction {
// val finalTx = super.call()
println("######## START CustomerIssueFlowResponder ########################################################")
println(ourIdentity)
val signTransactionFlow = object : SignTransactionFlow(otherSideSession) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
// val output = stx.tx.outputs.single().data
//"This must be an CustomerState." using (output is CustomerState)
}
}
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
val customerState = stx.tx.outputs.single().data as CustomerState
subFlow(CustomerPAHIssueOrUpdateFlow(customerState))
println("######## END CustomerIssueFlowResponder ########################################################")
return subFlow(signTransactionFlow)
}
}
And subflow
package th.co.jventures.ddlp.cordapp.flows
import co.paralleluniverse.fibers.Suspendable
import net.corda.core.contracts.Command
import net.corda.core.contracts.UniqueIdentifier
import net.corda.core.contracts.requireThat
import net.corda.core.flows.*
import net.corda.core.identity.Party
import net.corda.core.transactions.SignedTransaction
import net.corda.core.transactions.TransactionBuilder
import net.corda.core.utilities.ProgressTracker
import net.corda.core.utilities.loggerFor
import th.co.jventures.ddlp.cordapp.contracts.CustomerContract
import th.co.jventures.ddlp.cordapp.core.commons.CordappConstants
import th.co.jventures.ddlp.cordapp.services.CustomerService
import th.co.jventures.ddlp.cordapp.states.CustomerState
#InitiatingFlow
#StartableByService
#StartableByRPC
class CustomerPAHIssueOrUpdateFlow(private val customerState: CustomerState) : FlowLogic<String>() {
companion object {
val log = loggerFor<CustomerPAHIssueOrUpdateFlow>()
object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on new IOU.")
object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.")
object SIGNING_TRANSACTION : ProgressTracker.Step("Signing transaction with our private key.")
object GATHERING_SIGS : ProgressTracker.Step("Gathering the counterparty's signature.") {
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
}
object FINALISING_TRANSACTION : ProgressTracker.Step("Obtaining notary signature and recording transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
GENERATING_TRANSACTION,
VERIFYING_TRANSACTION,
SIGNING_TRANSACTION,
GATHERING_SIGS,
FINALISING_TRANSACTION
)
}
override val progressTracker = tracker()
#Suspendable
override fun call(): String {
// Obtain a reference to the notary we want to use.
val notary = serviceHub.networkMapCache.notaryIdentities[0]
// Stage 1.
progressTracker.currentStep = GENERATING_TRANSACTION
// Generate an unsigned transaction.
val customerService = serviceHub.cordaService(CustomerService::class.java)
//#################################################################//
val pahNode = serviceHub.networkMapCache.getPeerByLegalName(CordappConstants.Node.PAH) as Party
//val participants = listOf(ourIdentity, pahNode)
val customerStateAndRef = customerService.findByIdCardNoAndPartner(idCarNo = customerState.idCardNo!!, partner = CordappConstants.PartnerNameList.PAH)
val txBuilder = TransactionBuilder(notary)
if (customerStateAndRef == null) {
log.info("===================================================")
val linearId = UniqueIdentifier()
val customerOutputState = customerState.copy(linearId = linearId, participants = listOf(pahNode), partner = CordappConstants.PartnerNameList.PAH)
val txCommand = Command(CustomerContract.Commands.Issue(), customerState.participants.map { it.owningKey })
log.info("Create new JVC Customer : {}", customerOutputState)
txBuilder.addOutputState(customerOutputState, CustomerContract.CONTRACT_ID)
.addCommand(txCommand)
log.info("===================================================")
} else {
log.info("===================================================")
val txCommand = Command(CustomerContract.Commands.Update(), customerState.participants.map { it.owningKey })
val customerOutputState = customerState.copy(participants = listOf(pahNode), partner = CordappConstants.PartnerNameList.PAH)
log.info("Update existing JVC Customer : {}", customerOutputState)
txBuilder.addInputState(customerStateAndRef)
txBuilder.addOutputState(customerOutputState, CustomerContract.CONTRACT_ID)
.addCommand(txCommand)
log.info("===================================================")
}
//#################################################################//
// Stage 2.
progressTracker.currentStep = VERIFYING_TRANSACTION
// Verify that the transaction is valid.
txBuilder.verify(serviceHub)
// Stage 3.
progressTracker.currentStep = SIGNING_TRANSACTION
// Sign the transaction.
val partSignedTx = serviceHub.signInitialTransaction(txBuilder)
// Stage 4.
progressTracker.currentStep = GATHERING_SIGS
// Send the state to the counterparty, and receive it back with their signature.
val session = (customerState.participants - ourIdentity).map { initiateFlow(it) }
val fullySignedTx = subFlow(CollectSignaturesFlow(partSignedTx, session, GATHERING_SIGS.childProgressTracker()))
// Stage 5.
progressTracker.currentStep = FINALISING_TRANSACTION
// Notarise and record the transaction in both parties' vaults.
/*For syndicate loan phase
//Initial default wallet with 0.00 THB
subFlow(WalletIssueFlow(WalletIssueData(
amount = BigDecimal.ZERO,
currency = DEFAULT_CURRENCY,
customerId = linearId.id.toString()
)))
*/
subFlow(FinalityFlow(fullySignedTx, FINALISING_TRANSACTION.childProgressTracker()))
return customerState.linearId.toString()
}
}
#InitiatedBy(CustomerPAHIssueOrUpdateFlow::class)
class CustomerPAHIssueOrUpdateFlowResponder(val otherPartyFlow: FlowSession) : FlowLogic<SignedTransaction>() {
#Suspendable
override fun call(): SignedTransaction {
val signTransactionFlow = object : SignTransactionFlow(otherPartyFlow) {
override fun checkTransaction(stx: SignedTransaction) = requireThat {
//val output = stx.tx.outputs.single().data
// "This must be an CustomerState." using (output is CustomerState)
}
}
return subFlow(signTransactionFlow)
}
}
This issue is not related to calling a subflow from a flow. Instead, the issue is in this line in CustomerIssueFlowResponder:
val stx = subFlow(ReceiveTransactionFlow(otherSideSession, checkSufficientSignatures = false))
There is no corresponding send of a transaction from the other side of the flow, causing an exception of the type:
java.io.NotSerializableException: Described type with descriptor
net.corda:Rd6hxg+0oJROxKDXK8OerA== was expected to be of type class
net.corda.core.transactions.SignedTransaction but was java.util.List
Instead of calling ReceiveTransactionFlow, just use the transaction returned by SignTransactionFlow.
Related
I am trying to get the user uid after creating an account, so I can create a document on Firestore with the uid as document id. The problem is that I get only null, everything is working fine and I'm able to sign the user and receive the code but the uid is always null.
I know I'm accessing the uid before it's initialized because it takes time to restore the authentication state when the app starts so any idea how to wait for it until it's initialized?
First of all, if the data entered is correct then we call login() function, then we send the verification code, when the code is sent inside the onCodeSent(), the method we call uploadSelectedImageToFirebaseStorage() to upload the user image then we call saveUserToDatabase() function, and here is the problem. Inside the saveUserToDatabase() function, the UID is always null no matter what I tried, am I missing something?
#file:Suppress("DEPRECATION")
class SignUpActivity : AppCompatActivity() {
private lateinit var binding: ActivitySignUpBinding
private lateinit var auth: FirebaseAuth
private lateinit var callbacks: PhoneAuthProvider.OnVerificationStateChangedCallbacks
lateinit var storedVerificationId: String
lateinit var resendToken: PhoneAuthProvider.ForceResendingToken
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivitySignUpBinding.inflate(LayoutInflater.from(this))
auth = FirebaseAuth.getInstance()
setContentView(binding.root)
val flProfilePicture = binding.frameProfilePicture
val tilUserName = binding.tilUserName
val tilPhoneNumber = binding.tilPhoneNumber
val startButton = binding.startButton
val currentUser = auth.currentUser
if (currentUser != null) {
val intent = Intent(this#SignUpActivity, MainChatsActivity::class.java)
startActivity(intent)
finish()
}
callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() {
override fun onVerificationCompleted(credential: PhoneAuthCredential) { }
override fun onVerificationFailed(e: FirebaseException) { }
override fun onCodeSent(
verificationId: String,
token: PhoneAuthProvider.ForceResendingToken,
) {
Toast.makeText(baseContext, "Code Sent", Toast.LENGTH_SHORT).show()
storedVerificationId = verificationId
resendToken = token
val intent = Intent(applicationContext, AuthenticatePhoneActivity::class.java)
intent.putExtra("storedVerificationId", storedVerificationId)
uploadSelectedImageToFirebaseStorage()
startActivity(intent)
finish()
}
}
flProfilePicture.setOnClickListener {
val intent = Intent(Intent.ACTION_PICK)
intent.type = "image/*"
startActivityForResult(intent, 0)
}
startButton.setOnClickListener {
if (TextUtils.isEmpty(tilUserName.text.toString())) {
tilUserName.error = "Enter valid Name"
}
if (TextUtils.isEmpty(tilPhoneNumber.text.toString())) {
tilPhoneNumber.error = "Enter valid phone number"
} else {
val userName: String = tilUserName.text.toString()
val userPhoneNumber: String = tilPhoneNumber.text.toString()
login()
}
}
}
private var selectedProfilePicture: Uri? = null
#Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 0 && data != null) {
selectedProfilePicture = data.data
val bitmap = MediaStore.Images.Media.getBitmap(contentResolver, selectedProfilePicture)
val flProfilePicture = binding.frameProfilePicture
val selectedCircleFrame = binding.selectedPictureCircleFrame
selectedCircleFrame.setImageBitmap(bitmap)
flProfilePicture.alpha = 0f
}
}
private fun login() {
val mobileNumber = binding.tilPhoneNumber
val number = mobileNumber.text.toString().trim()
if (number.isNotEmpty()) {
sendVerificationCode(number)
} else {
mobileNumber.error = "Enter a valid phone number"
}
}
private fun sendVerificationCode(number: String) {
val options = PhoneAuthOptions.newBuilder(auth)
.setPhoneNumber(number)
.setTimeout(60L, TimeUnit.SECONDS)
.setActivity(this)
.setCallbacks(callbacks)
.build()
PhoneAuthProvider.verifyPhoneNumber(options)
}
private fun uploadSelectedImageToFirebaseStorage() {
if (selectedProfilePicture == null) {
return
}
val fileName = UUID.randomUUID().toString()
val ref = FirebaseStorage.getInstance().getReference("/images/$fileName")
ref.putFile(selectedProfilePicture!!)
.addOnSuccessListener {
ref.downloadUrl.addOnSuccessListener {
it.toString()
Log.d("SignUpActivity", "image uploaded successfully")
saveUserToDatabase(it.toString())
}
}
.addOnFailureListener {
saveUserToDatabase(it.toString())
}
}
private fun saveUserToDatabase(profileImageUrl: String) {
val tilUserName = binding.tilUserName.text.toString()
val tilPhoneNumber = binding.tilPhoneNumber.text.toString()
val uid = FirebaseAuth.getInstance().uid.toString()
val database = Firebase.database("https://blend-4a9e4-default-rtdb.asia-southeast1.firebasedatabase.app")
val myRef = database.getReference("/users/$uid")
val user = User(uid, tilPhoneNumber, tilUserName, profileImageUrl)
Log.d("currentUser", uid)
myRef.setValue(user)
.addOnFailureListener {
Toast.makeText(baseContext, "Something went wrong, try again.", Toast.LENGTH_SHORT).show()
}
}
}
I am using recycler view to load documents from firestore. Now my recycler view lists has three buttons. One of the button name is notify. When I click notify button(btn_notify) I want to make the button visibity to gone using holder.button.visibility = View.GONE. But what happens is that when I change the button visibility to gone, the clicked button visibility changes to gone. At the same time the other recycler view lists button visibility also changes to gone. Why this error is happening.
[![enter image description here][1]][1]
package com.example.bloodbankcompany.recyclerview
import android.annotation.SuppressLint
import android.content.Intent
import android.net.Uri
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.bloodbankcompany.*
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
import com.google.firebase.messaging.FirebaseMessaging
import com.google.gson.Gson
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.lang.Exception
const val TOPIC = "/topics/myTopic"
class MyAdapter2(private val userList: ArrayList<User2>): RecyclerView.Adapter<MyAdapter2.MyViewHolder>(){
var id: String = ""
private lateinit var userArrayList: ArrayList<User2>
private val mFireStore = FirebaseFirestore.getInstance()
val TAG = "MyAdapter2"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyAdapter2.MyViewHolder {
val itemView = LayoutInflater.from(parent.context).inflate(R.layout.list_item2,parent,false)
return MyViewHolder(itemView)
}
//
// interface OnItemClickListener {
// fun onItemClick(position: Int)
// }
override fun onBindViewHolder(holder: MyAdapter2.MyViewHolder, position: Int) {
// holder.bind( position ,onItemClickListener)
val user:User2 = userList[position]
holder.name.text= user.name
holder.phone.text= user.phone
holder.address.text =user.address
holder.bloodGroup.text= user.bloodgroup
holder.id.text = user.id
holder.status.text= user.status
holder.donatedby.text = user.donatedBy.toString()
val statust = user.status?.trim()
val requester_name = user.name
val requester_no = user.phone
holder.emailn.text= user.email
val idp: String? =user.id
val phonen: String? =user.phone
val emailt: String? = user.email
holder.button.setOnClickListener {
holder.button.visibility = View.GONE
val washingtonre = mFireStore.collection("applicationForm").document("$idp")
washingtonre.update("status","Verified").addOnCompleteListener {
holder.status.text = "Verified"
val title = "New blood donation request."
val message = "Please contact $requester_name at $requester_no."
//val recipientToken = etToken.text.toString()
if(title.isNotEmpty() && message.isNotEmpty()) {
PushNotificationNotif(
NotificationDataNotif(title, message),
TOPIC
// recipientToken
).also {
sendNotification(it)
}
}
}
}
holder.phonen.setOnClickListener {
val context=holder.button.context
val intent1 = Intent(Intent.ACTION_DIAL)
intent1.data = Uri.parse("tel:" + phonen)
context.startActivity(intent1)
}
holder.bemail.setOnClickListener {
val context1=holder.button.context
val intent= Intent(Intent.ACTION_SEND)
intent.data = Uri.parse("mailto:" + emailt)
intent.type = "text/plain"
intent.putExtra(Intent.EXTRA_EMAIL, emailt)
intent.putExtra(Intent.EXTRA_SUBJECT, " ")
intent.putExtra(Intent.EXTRA_TEXT, " ")
intent.setType("message/rfc822")
try {
// context1.startActivity(Intent.createChooser(intent, "Choose email Client"))
context1.startActivity(intent)
} catch (e: Exception){
}
}
}
#SuppressLint("LongLogTag")
private fun sendNotification(notification: PushNotificationNotif) = CoroutineScope(
Dispatchers.IO).launch {
try {
val response = RetrofitInstanceNotif.api.postNotification(notification)
if(response.isSuccessful) {
Log.d(TAG, "Response: ${Gson().toJson(response)}")
} else {
Log.e(TAG, response.errorBody().toString())
}
} catch(e: Exception) {
Log.e(TAG, e.toString())
}
}
override fun getItemCount(): Int {
return userList.size
}
public class MyViewHolder(itemView : View): RecyclerView.ViewHolder(itemView){
// fun bind(position: Int, listener: OnItemClickListener) {
// button.setOnClickListener { v -> listener.onItemClick(position) }
// }
val name : TextView = itemView.findViewById(R.id.tvfirstname1)
val phone :TextView= itemView.findViewById(R.id.tvphone11)
val address : TextView= itemView.findViewById(R.id.tvaddress1)
val bloodGroup: TextView =itemView.findViewById(R.id.tvbloodg1)
val id: TextView = itemView.findViewById(R.id.tvid)
val status: TextView = itemView.findViewById(R.id.tvstatus1)
// val donationsta: TextView = itemView.findViewById(R.id.tvdonated1)
var io: String? = ""
val button: Button = itemView.findViewById(R.id.btn_notify)
val phonen: Button =itemView.findViewById(R.id.btn_phone1)
val emailn: TextView = itemView.findViewById(R.id.tvemail1)
val bemail: TextView = itemView.findViewById(R.id.btn_email1)
val donatedby: TextView=itemView.findViewById(R.id.tvdonated1)
}
} ```
/** This is the recyclerview activity code
package com.example.bloodbankcompany.recyclerview
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.WindowManager
import android.widget.Adapter
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.bloodbankcompany.MyAdapter
import com.example.bloodbankcompany.R
import com.example.bloodbankcompany.User1
import com.example.bloodbankcompany.User2
import com.google.firebase.firestore.*
import kotlinx.android.synthetic.main.activity_blood_application_form.*
import kotlinx.android.synthetic.main.activity_main2.*
import java.lang.NullPointerException
class MainActivity2 : AppCompatActivity() {
private lateinit var recyclerView: RecyclerView
private lateinit var userArrayList: ArrayList<User2>
private lateinit var myAdapter: MyAdapter2
private val mFireStore = FirebaseFirestore.getInstance()
var db = FirebaseFirestore.getInstance()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
window.setFlags(
WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN
)
// setupActionBar()
recyclerView= findViewById(R.id.recyclerview)
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
userArrayList= arrayListOf()
myAdapter =MyAdapter2(userArrayList)
recyclerView.adapter = myAdapter
EventChangeListener()
}
private fun EventChangeListener() {
try {
mFireStore.collection("applicationForm").addSnapshotListener(object :
EventListener<QuerySnapshot> {
override fun onEvent(value: QuerySnapshot?, error: FirebaseFirestoreException?) {
if (error != null) {
Log.e("firestore error", error.message.toString())
}
try {
for (dc: DocumentChange in value?.documentChanges!!) {
if (dc.type == DocumentChange.Type.ADDED) {
userArrayList.add(dc.document.toObject(User2::class.java))
}
// Toast.makeText(applicationContext,userArrayList.toString(), Toast.LENGTH_SHORT).show()
}
} catch (e:NullPointerException){
}
myAdapter.notifyDataSetChanged()
}
})
} catch (e:NullPointerException){
}
}
}
[1]: https://i.stack.imgur.com/loob8.jpg
I create retrofitconfig for get data from API in first time I use it works and good but in 2 days data not showing from Api
this is my interface
interface CnnApi {
#GET("cnbc-news/")
fun getNewsCNN(): Call<NewsResponse>
#GET("cnbc-news/{type}")
fun getNewsCCNType(#Path("type") type: String ) : Call<NewsResponse>
}
and then this is object I use to make retrofit
object RetrofitConfig {
fun getData(): CnnApi {
val loggingInterceptor =
HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
val client = OkHttpClient.Builder()
.addInterceptor(loggingInterceptor)
.build()
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.baseUrl("https://berita-indo-api.vercel.app/v1/")
.client(client)
.build()
return retrofit.create(CnnApi::class.java)
}
}
and then this is my HomeViewModel
class HomeViewModel : ViewModel() {
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
private val _dataItem = MutableLiveData<List<DataItem>>()
val dataItem: LiveData<List<DataItem>> = _dataItem
private val _image = MutableLiveData<Image>()
val image:LiveData<Image> = _image
companion object{
private const val TAG = "News"
private const val type ="lifestyle"
}
init{
getDataFromAPI()
}
private fun getDataFromAPI(){
EspressoIdlingResource.increment()
_isLoading.value = true
val client = RetrofitConfig.getData().getNewsCCNType(type)
client.enqueue(object: Callback<NewsResponse>{
override fun onResponse(call: Call<NewsResponse>, response: Response<NewsResponse>) {
_isLoading.value = false
if(response.isSuccessful || response.body()?.total== 200){
_dataItem.value = response.body()?.data
}else{
Log.e(TAG,"onFailure: ${response.message()}")
}
}
override fun onFailure(call: Call<NewsResponse>, t: Throwable) {
_isLoading.value = false
Log.e(TAG, "onFailure: ${t.message.toString()}")
EspressoIdlingResource.decrement()
}
})
}
}
This is the view or how I show the data
homeViewModel =
ViewModelProvider(this).get(HomeViewModel::class.java)
_binding = FragmentHomeBinding.inflate(inflater, container, false)
val root: View = binding.root
homeViewModel.dataItem.observe(viewLifecycleOwner, { news ->
binding.articleTitle.text = news[0].title
Glide.with(this)
.load(news[0].image.small)
.into(binding.articleImage)
binding.articleFrom.text = "CNN Indonesia"
link = news[0].link
})
binding.imageCard.setOnClickListener {
val intent = Intent(Intent.ACTION_VIEW)
intent.data = Uri.parse(link)
startActivity(intent)
}
Logcat or error
logcat is what I think makes the application so it doesn't display the desired data, how to solve must-revalidate data ? because I think because of it I cant show the data from API
I'm creating my model with modelview and services. This is the best way to create with swipeRefresh
I don't know if I'm doing it the right way so I'm asking for your help
who can give feedback thank you very much.
In fact everything is working perfectly I'm just in doubt about this part:
val viewModel: MyViewModel = viewModel()
val isRefreshing by viewModel.isRefreshing.collectAsState()
my method SwipeRefresh:
#Composable
fun SwipeRefresh( content: #Composable (lists:ArrayList<ShoppingCart>) -> Unit){
val viewModel: MyViewModel = viewModel()
val isRefreshing by viewModel.isRefreshing.collectAsState()
com.google.accompanist.swiperefresh.SwipeRefresh(
state = rememberSwipeRefreshState(isRefreshing),
onRefresh = { viewModel.refresh() },
indicator = { state, trigger ->
SwipeRefreshIndicator(
// Pass the SwipeRefreshState + trigger through
state = state,
refreshTriggerDistance = trigger,
// Enable the scale animation
scale = true,
// Change the color and shape
shape = MaterialTheme.shapes.small,
)
}
) {
if(isRefreshing){
if(tipos=="frutas"){
tipos = "fruta"
}
frutas.tipo=tipos
frutas.getFrutas()
when(tipos){
"saladas"->{
val s by frutas.saladasData.collectAsState(initial = emptyList())
list = ArrayList(s)
}
"fruta"->{
val f by frutas.frutaData.collectAsState(initial = emptyList())
list = ArrayList(f)
}
"temperos"->{
val t by frutas.temperosData.collectAsState(initial = emptyList())
list = ArrayList(t)
}
}
}
Log.i("LIST",list.toString())
val t = list.filter { it.product_title!="" }
if(list.isNotEmpty() && t.isNotEmpty()){
content(list)
}else{
helpers.loadingComponent()
}
}
}
My ViewModel:
package com.example.quitanda.models
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch
class FrutasViewModel(
private val frutasServices: Services,
):ViewModel() {
private val _frutasData: MutableStateFlow<List<ShoppingCart>> = MutableStateFlow(listOf(ShoppingCart()))
val frutasData: StateFlow<List<ShoppingCart>>
get() = _frutasData
private val _frutaData: MutableStateFlow<List<ShoppingCart>> = MutableStateFlow(listOf(ShoppingCart()))
val frutaData: StateFlow<List<ShoppingCart>>
get() = _frutaData
private val _saladasData: MutableStateFlow<List<ShoppingCart>> = MutableStateFlow(listOf(ShoppingCart()))
val saladasData: StateFlow<List<ShoppingCart>>
get() = _saladasData
private val _temperosData: MutableStateFlow<List<ShoppingCart>> = MutableStateFlow(listOf(ShoppingCart()))
val temperosData: StateFlow<List<ShoppingCart>>
get() = _temperosData
lateinit var tipo:String
fun getFrutas(){
viewModelScope.launch {
try {
when(tipo){
"fruta"->{
val fruta = frutasServices.getFrutas1()
_frutaData.value = fruta
}
"frutas"->{
val frutas = frutasServices.getFruta()
_frutasData.value = frutas
}
"saladas"->{
val saladas = frutasServices.getSaladas()
_saladasData.value = saladas
}
"temperos"->{
val temperos = frutasServices.getTemperos()
_temperosData.value = temperos
}
}
}catch (e:Exception){
Log.d("Service error",e.toString())
}
}
}
}
Service:
package com.example.quitanda.models
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import retrofit2.http.GET
interface Services {
#GET("category/")
suspend fun getFruta(): List<ShoppingCart>
#GET("category/7")
suspend fun getFrutas1(): List<ShoppingCart>
#GET("category/8")
suspend fun getSaladas(): List<ShoppingCart>
#GET("category/9")
suspend fun getTemperos(): List<ShoppingCart>
}
val retrofit: Retrofit = Retrofit.Builder()
.baseUrl("http://192.168.2.157:4000/")
.addConverterFactory(MoshiConverterFactory.create())
.build()
val frutasServices: Services = retrofit.create(Services::class.java)
My Activity:
package com.example.quitanda
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Menu
import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.ShoppingCart
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.NavController
import androidx.navigation.compose.*
import com.example.quitanda.classes.EntryAbstract
import com.example.quitanda.classes.Helpers
import com.example.quitanda.models.FrutasViewModel
import com.example.quitanda.models.ShoppingCart
import com.example.quitanda.models.frutasServices
import com.google.accompanist.pager.ExperimentalPagerApi
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
#Suppress("UNCHECKED_CAST")
#ExperimentalPagerApi
class MainActivity : ComponentActivity() {
private val entryAbstract = EntryAbstract()
private var counter = mutableStateOf(0)
private var ids = mutableStateListOf<ShoppingCart>()
private lateinit var listFrutas:ArrayList<ShoppingCart>
private lateinit var listSaladas:ArrayList<ShoppingCart>
private lateinit var listChas:ArrayList<ShoppingCart>
private val helpers = Helpers()
private val viewModel by viewModels<FrutasViewModel> {
object : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
return FrutasViewModel(frutasServices) as T
}
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent{
if(intent.hasExtra("produtos")) {
ids = remember { intent.getParcelableArrayListExtra<ShoppingCart>("produtos")!!
.toMutableStateList()}
if (ids.isNotEmpty()) {
val aray = ids.count()
counter.value = aray
}
}
viewModel.tipo="frutas"
viewModel.getFrutas()
val l by viewModel.frutasData.collectAsState(initial = emptyList())
if (l.isEmpty()) {
helpers.loadingComponent()
} else {
val i = l.filter { it.category_id==7 }
val o = l.filter { it.category_id==8 }
val p = l.filter { it.category_id==9 }
listFrutas = ArrayList(i)
listSaladas = ArrayList(o)
listChas = ArrayList(p)
entryAbstract.cart =ids
entryAbstract.ccounter =counter
entryAbstract.frutas = viewModel
MainContent()
}
}
}
ShoppingCart:
package com.example.quitanda.models
import android.os.Parcelable
import com.squareup.moshi.Json
import kotlinx.parcelize.Parcelize
#Parcelize
data class ShoppingCart(
var count:Int=0,
#field:Json(name="product_title")
var product_title:String="",
#field:Json(name="product_id")
var product_id:Int=0,
#field:Json(name="photo_photo")
var photo_photo:String="",
#field:Json(name="product_quant")
var product_quant:Int=0,
#field:Json(name="category_name")
var category_name:String="",
#field:Json(name="category_id")
var category_id:Int=0,
#field:Json(name="product_description")
var product_description:String="",
#field:Json(name="product_price_un")
var product_price_un:String?="",
#field:Json(name="product_price_kg")
var product_price_kg:String?="",
var tipos:String=""): Parcelable
I understand seeing my MyViewModel
class MyViewModel : ViewModel() {
private val _isRefreshing = MutableStateFlow(false)
val isRefreshing: StateFlow<Boolean>
get() = _isRefreshing.asStateFlow()
fun refresh() {
// This doesn't handle multiple 'refreshing' tasks, don't use this
viewModelScope.launch {
// A fake 2 second 'refresh'
_isRefreshing.emit(true)
delay(2000)
_isRefreshing.emit(false)
}
}
}
how I make a viewmodel with multiple returns is a little different
I'm trying to build a corda application for training porpuses. In this application i need to transfer
some coins to a new owner, and the states that keeps these coins has to be unique, if you have a previous coin state you will need to evolve it. For example, if someone transfer to you 300 coins and you already have 200 you will evolve this state of 200 coins to a new one of 500.
I've already tried to create other subflows to get the responder inputs to the transaction but it didn't work. Bellow you guys can see my request and response flow without handling this situation that i asked above. Can you guys have an ideia of how can i handle this situation?
Thank you guys so much and sorry about some english issues that my text can have.
#InitiatingFlow
#StartableByRPC
class CoinTransferFlow(var amount: Double, val newOwner: Party) : FlowLogic<SignedTransaction>() {
companion object {
object GENERATING_TRANSACTION : ProgressTracker.Step("Generating transaction based on new BrunoCoin.")
object VERIFYING_TRANSACTION : ProgressTracker.Step("Verifying contract constraints.")
object SIGNING_TRANSACTION : ProgressTracker.Step("Signing transaction with our private key.")
object GETTING_OTHER_SIGNATURES : ProgressTracker.Step("Gathering the counterparty's signature.") {
override fun childProgressTracker() = CollectSignaturesFlow.tracker()
}
object FINALISING_TRANSACTION : ProgressTracker.Step("Obtaining notary signature and recording transaction.") {
override fun childProgressTracker() = FinalityFlow.tracker()
}
fun tracker() = ProgressTracker(
GENERATING_TRANSACTION,
VERIFYING_TRANSACTION,
SIGNING_TRANSACTION,
GETTING_OTHER_SIGNATURES,
FINALISING_TRANSACTION
)
}
override val progressTracker = tracker()
#Suspendable
override fun call(): SignedTransaction {
val listMoneyStateAndRef = serviceHub.vaultService.queryBy(BrunoCoinState::class.java).states
val notary = serviceHub.networkMapCache.notaryIdentities[0]
progressTracker.currentStep = GENERATING_TRANSACTION
var txBuilder = buildTransaction(listMoneyStateAndRef, notary)
progressTracker.currentStep = VERIFYING_TRANSACTION
txBuilder.verify(serviceHub)
progressTracker.currentStep = SIGNING_TRANSACTION
val signedTransaction = serviceHub.signInitialTransaction(txBuilder)
progressTracker.currentStep = GETTING_OTHER_SIGNATURES
progressTracker.currentStep = FINALISING_TRANSACTION
val otherPartySession = initiateFlow(newOwner)
otherPartySession.send(signedTransaction)
return subFlow(FinalityFlow(signedTransaction, setOf(otherPartySession)))
}
#InitiatedBy(CoinTransferFlow::class)
class TransferResponderFlow(val otherPartySession: FlowSession) : FlowLogic<SignedTransaction>() {
companion object {
object VERIFYING_TRANSACTION : ProgressTracker.Step("Counterparty verifying contract constraints.")
object FINALISING_TRANSACTION : ProgressTracker.Step("Counterparty finalising the transaction")
fun tracker() = ProgressTracker(
VERIFYING_TRANSACTION,
FINALISING_TRANSACTION
)
}
override val progressTracker = tracker()
#Suspendable
override fun call(): SignedTransaction {
fun verifyTx(sgdTx : SignedTransaction) = requireThat {
"O output precisa ser do tipo BrunoCoinState" using (sgdTx.tx.outputStates[0] is BrunoCoinState)
val bCoinState = sgdTx.tx.outputStates[0] as BrunoCoinState
"O output2 precisa ser do tipo BrunoCoinTransferState" using (sgdTx.tx.outputStates[1] is BrunoCoinTransferState)
val bCoinTransferState = sgdTx.tx.outputStates[1] as BrunoCoinTransferState
"Os valores propostos na transação deve ser maior que 0" using (bCoinState.amount > 0
&& bCoinTransferState.amount > 0)
"Os valores propostos na transação e o valor enviados devem ser iguais" using (bCoinState.amount == bCoinTransferState.amount)
}
val sgdTx = otherPartySession.receive<SignedTransaction>().unwrap{ it }
progressTracker.currentStep = VERIFYING_TRANSACTION
verifyTx(sgdTx)
progressTracker.currentStep = FINALISING_TRANSACTION
return subFlow(ReceiveFinalityFlow(otherPartySession/*, expectedTxId = txId*/))
}
}
addInputState adds a referenced input:
/** Adds an input [StateRef] to the transaction. */
open fun addInputState(stateAndRef: StateAndRef<*>) = apply {
checkNotary(stateAndRef)
inputs.add(stateAndRef.ref)
inputsWithTransactionState.add(stateAndRef)
resolveStatePointers(stateAndRef.state)
return this
}
See an example: https://github.com/corda/samples/blob/release-V4/auction-cordapp/workflows/src/main/java/net/corda/samples/flows/BidFlow.java#L70