I don't know how to make my failure toast message to show only once.
Toast.makeText(this, vm.logInResult.value, Toast.LENGTH_SHORT).show()
private fun addData(edtTxt: String, pasTxt: String) {
val repository = UserRepository()
val viewModelFactory = UserViewModelFactory(repository)
viewModel = ViewModelProvider(this, viewModelFactory).get(UserViewModel::class.java)
viewModel.pushUser(edtTxt, pasTxt)
viewModel.userPush.observe(this, Observer { response ->
if (response.isSuccessful) {
dismissLogoProgressDialog()
Log.d("MainResponse", response.body().toString())
Log.d("MainExecute", response.code().toString())
Log.d("Main", response.message())
val check = response.body()
Log.d("checkdata", "" + check?.userinfo?.email)
val tokn: String = check!!.token
if (sharedPreference.getValueString("token") != null) {
sharedPreference.clearSharedPreference()
}
sharedPreference.save("token", tokn)
sharedPreference.save("login_uid", check.userinfo.uid)
sharedPreference.save("change_pass", pasTxt)
println(check)
startActivity(Intent(this, DashboardActivity::class.java))
finish()
} else {
dismissLogoProgressDialog()
Toast.makeText(this, "Password mismatch", Toast.LENGTH_SHORT).show()
}
})
}
Are you sure you only call this Toast once? Or is this Toast created in a loop? In that case; you need to breakout of the loop first.
The function may have been placed within a loop and the else clause is may always be taken.
Are the Log functions printing anything to the console?
Is there anyway you could edit the question and show us where this function is called?
Related
How can I prevent having deeply nested callbacks when using the Firebase/Firestore API?
Py app call firestore api step by step, and i need do something process when onSuccess and onFailed.
For example, it need 5 steps. For going to the next step, it needs reference to pre-call api's result.
1st Step : getA() // get A Data from firestore
2nd Step : if(resultGetA.isSuccess) getB() else updateC()
3rd Step : if(resultGetB.isSuccess) addD()
4th Step : if(resultAddD.isSuccess) updateE()
5th Step : if(resultUpdateE.isSuccess) getF()
example to kotlin code
This is just an example source for explain my question,
but my application's code similar like this :(
fun callHellExample1(email:String, pass:String, model:UserDataModel) {
val collectRef = Firebase.firestore.collection("A")
val auth = Firebase.auth
auth.createUserWithEmailAndPassword(email, pass).addOnCompleteListener { createTask ->
if (createTask.isSuccessful) {
auth.signInWithEmailAndPassword(email, pass).addOnCompleteListener { signInTask ->
if (signInTask.isSuccessful) {
collectRef.add(model).addOnCompleteListener {
Toast.makeText(this, "complete create account", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
}
}
} else {
Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
}
}
}
fun callHellExample2(callback : (Boolean)-> Unit) {
val collectRef = Firebase.firestore.collection("A")
val auth = Firebase.auth
collectRef.document("A").get().addOnCompleteListener { resultA ->
if(resultA.isSuccessful){
collectRef.document("B").get().addOnCompleteListener { resultB ->
if(resultB.isSuccessful){
collectRef.add("D").addOnCompleteListener { resultD ->
if(resultD.isSuccessful){
collectRef.document("E").update("someFiled", "someValue").addOnCompleteListener { resultE ->
if(resultE.isSuccessful){
collectRef.document("F").get().addOnCompleteListener {
auth.signOut()
callback(true)
}
}
}
}
}
}else{
Toast.makeText(this, "getB ... isSuccessful? = ${resultB.isSuccessful}", Toast.LENGTH_SHORT).show()
}
}
}else{
collectRef.document("C").update("someFiled", "someValue").addOnCompleteListener { resultC ->
Toast.makeText(this, "update C ... isSuccessful? = ${resultC.isSuccessful}", Toast.LENGTH_SHORT).show()
}
}
}
}
so i try use by coroutine. but i can't found escape callback hell about firestore api
i tried like this (example 1). but it similar like callback hell.
i want check it successful.
await() return not Task<AuthResult>. it just return AuthResult
but AuthResult is not contains isSuccessful variable
fun example1ByCoroutine(email:String, pass:String, mode:UserModel){
CoroutineScope(Dispatchers.IO).launch {
try{
auth.createUserWithEmailAndPassword(email, pass).await()
try{
auth.signInWithEmailAndPassword(email, pass).await()
try{
collectRef.add(model).await()
withContext(Dispatchers.Main){
Toast.makeText(this, "complete create account", Toast.LENGTH_SHORT).show()
}
}catch (e: Exception){
Toast.makeText(this, "failed create account in Step 3", Toast.LENGTH_SHORT).show()
}
}catch (e: Exception){
Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
}
}catch (e: Exception){
Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
}
}
}
example 2 is can not show toast cuz can not check isSuccessful too.
is not return Task. it just return DocumentSnapShot
i look forward to your reply.
thanks!
ps) if can access to isSuccessful, code can be edit like this
fun example1ByCoroutine(email:String, pass:String, mode:UserModel){
CoroutineScope(Dispatchers.IO).launch {
if(!auth.createUserWithEmailAndPassword(email, pass).await().isSuccessful){
Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
return#launch
}
if(!auth.signInWithEmailAndPassword(email, pass).await().isSuccessful){
Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
return#launch
}
if(collectRef.add(model).await().isSuccessful){
Toast.makeText(this, "failed create account in Step 3", Toast.LENGTH_SHORT).show()
return#launch
}
withContext(Dispatchers.Main){
Toast.makeText(this, "complete create account", Toast.LENGTH_SHORT).show()
}
}
}
I would not advice to re-implement the Task.await() method to return the task itself again, instead you can add a simple wrapper to the standard kotlin Result class:
import com.google.android.gms.tasks.Task
import kotlinx.coroutines.tasks.await
suspend fun <T> Task<T>.awaitResult() = runCatching { await() }
And then use it like this:
suspend fun foo(email: String, pass: String){
if(auth.createUserWithEmailAndPassword(email, pass).awaitResult().isFailure){
Toast.makeText(this, "failed create account in Step 1", Toast.LENGTH_SHORT).show()
return
}
if(auth.signInWithEmailAndPassword(email, pass).awaitResult().isFailure){
Toast.makeText(this, "failed create account in Step 2", Toast.LENGTH_SHORT).show()
return
}
}
Coroutines have integration with Google Play Services Tasks API.
Just add this dependency:
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.4.1'
You can convert a Task into a Deferred using Task.asDeferred.
Similarly, Task.await Awaits for completion of the Task (cancellable).
You can read more about it here : https://github.com/Kotlin/kotlinx.coroutines/tree/master/integration/kotlinx-coroutines-play-services
I am building an android app in MVVM architecture using Firebase. I am trying to do User's password change and whenever i start my code, application freezes or just stops responding. I spent a lot of time to search what is wrong with it and yet no fix. If anyone know why it behave like this I would appreciate your help. My code:
Function called in fragment:
private fun startChangePasswordDialog(){
val dialogView = LayoutInflater.from(activity).inflate(R.layout.dialog_change_password, null)
val builder = AlertDialog.Builder(activity).setView(dialogView)
val dialog: AlertDialog = builder.show()
val changePassword = dialogView.findViewById<Button>(R.id.changePasswordBT)
val cancel = dialogView.findViewById<Button>(R.id.changePasswordCancelBT)
val passwordET = dialogView.findViewById<EditText>(R.id.changePasswordET)
changePassword?.setOnClickListener {
val newPassword = passwordET.text.trim().toString()
if (TextUtils.isEmpty(newPassword) || newPassword.length < viewModel.PASSWORD_MIN_VALUE){
Toast.makeText(requireContext(), R.string.password_too_short, Toast.LENGTH_SHORT).show()
}
else{
viewModel.changeUsersPassword(newPassword)
viewModel.needUserAuthentication.observe(requireActivity(), {
if (it == true) reAuthenticateUser()
})
}
dialog.dismiss()
}
cancel?.setOnClickListener {
dialog.dismiss()
}
ViewModel function:
fun changeUsersPassword(password: String) {
Log.d(TAG,"Starting user's password change procedure")
when (repository.changeUserPassword(password)){
PasswordChangeCallbackEnum.FACEBOOK_USER -> {
_toastMessage.value = R.string.facebook_user_password_change
Log.d(TAG, "User's password will not be changed, logged in as Facebook user")
}
PasswordChangeCallbackEnum.PASSWORD_CHANGE_ERROR -> {
_toastMessage.value = R.string.password_change_error
Log.d(TAG, "Error while changing user's password")
}
PasswordChangeCallbackEnum.PASSWORD_CHANGED -> {
_toastMessage.value = R.string.password_change_success
Log.d(TAG, "User's password changed successfully")
}
PasswordChangeCallbackEnum.NEED_USER_AUTHENTICATION -> {
_needUserAuthentication.value = true
}
}
}
Firebase Repository (I have changed it several times when tried to fix this):
fun changeUserPassword(password: String): PasswordChangeCallbackEnum {
var result = PasswordChangeCallbackEnum.PASSWORD_CHANGE_ERROR
if (currentUser != null) {
for (userInfo in currentUser.providerData) {
if (userInfo.providerId == "facebook.com") {
Log.d(TAG, "Cannot change password for user logged in with facebook")
result = PasswordChangeCallbackEnum.FACEBOOK_USER
}
}
}
try{
val updateTask = authentication.currentUser?.updatePassword(password)
updateTask?.addOnSuccessListener {
Log.d(TAG, "User's password change state: SUCCESS")
result = PasswordChangeCallbackEnum.PASSWORD_CHANGED
}
}catch (exception: FirebaseAuthRecentLoginRequiredException){
Log.d(TAG, "Need user to authenticate again")
result = PasswordChangeCallbackEnum.NEED_USER_AUTHENTICATION
}
return result
}
The problem is, you are doing task in ui thread.
Use coroutines for the task to do in worker thread .
you can have more information about coroutines. here
You can also use RxJava for it or some Async task.
It will prevent the ui freezing
I'm trying to develop an app that:
Listens to push notifications
If the push notification is from WhatsApp + contains certain info, the app should call a specific number.
For the sake of the argument, let's assume that both permissions (call + notification listener) have already been granted.
So I used the below code (and of course, added the listener to the manifest), which works while the app is in the front, but not when it's in the background or closed. I also tried replacing "startActivity" with "startService", but that didn't work either. What's the correct way to leave the service running in the background and actually calling a number even though the app is in the background or closed? Also, is there a certain way to achieve this even the phone is locked?
class NotificationListener : NotificationListenerService() {
companion object {
private const val TAG = "NotificationListener"
private const val WA_PACKAGE = "com.whatsapp"
}
override fun onListenerConnected() {
Log.i(TAG, "Notification Listener connected")
Toast.makeText(applicationContext, "Notification Listener connected", Toast.LENGTH_SHORT).show()
}
override fun onNotificationPosted(sbn: StatusBarNotification) {
if (sbn.packageName != WA_PACKAGE) {
return
}
val notification = sbn.notification
val extras: Bundle = notification.extras
val from = extras.getString(NotificationCompat.EXTRA_TITLE)
val message = extras.getString(NotificationCompat.EXTRA_TEXT)
if (from != null && from.contains("test") && message != null && message.contains("gate")) {
val msg = "[$from]\n[$message]"
Log.i(TAG, msg)
Toast.makeText(applicationContext, msg, Toast.LENGTH_SHORT).show()
attemptCallGate()
}
}
private fun attemptCallGate() {
when (ContextCompat.checkSelfPermission(applicationContext, Manifest.permission.CALL_PHONE) == PackageManager.PERMISSION_GRANTED) {
true -> callGate()
false -> Toast.makeText(applicationContext, R.string.access_denied, Toast.LENGTH_SHORT).show()
}
}
private fun callGate() {
val number = "1234567890"
try {
val callIntent = Intent(Intent.ACTION_CALL, Uri.parse("tel:$number"))
callIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
Toast.makeText(applicationContext, "Attempting to call [$number]", Toast.LENGTH_SHORT).show()
startActivity(callIntent)
} catch (e: Exception) {
Toast.makeText(applicationContext, "Failed calling [$number] ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}
Situation
I submit data setTripDeliver, the collect works fine (trigger LOADING and then SUCCESS). I pressed a button go to next fragment B (using replace). After that, I press back button (using popbackstack). the collect SUCCESS triggered.
Codes Related
These codes at the FragmentA.kt inside onViewCreated.
private fun startLifeCycle() {
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch {
collectTripDeliver()
}
launch {
collectTripReattempt()
}
}
}
}
These codes when to submit data at a button setOnClickListener.
viewLifecycleOwner.lifecycleScope.launchWhenStarted {
viewModel.setTripDeliver(
verificationCode,
remark
)
}
Method to collect flow collectTripReattempt()
private suspend fun collectTripReattempt() {
viewModel.tripReattempt.collect {
when (it) {
is Resource.Initialize -> {
}
is Resource.Loading -> {
Log.i("???","collectTripReattempt loading")
handleSaveEarly()
}
is Resource.Success -> {
val error = it.data?.error
if (error == null) {
Tools.showToast(requireContext(), "Success Reattempt")
Log.i("???","collectTripReattempt Success")
} else {
Tools.showToast(requireContext(), "$error")
}
handleSaveEnding()
}
is Resource.Error -> {
handleSaveEnding()
}
}
}
}
Below codes are from ViewModel.
private val _tripDeliver =
MutableStateFlow<Resource<TripDeliverResponse>>(Resource.Initialize())
val tripDeliver: StateFlow<Resource<TripDeliverResponse>> = _tripDeliver
This method to call repository.
suspend fun setTripDeliver(
verificationCode: String?,
remark: String?
) {
_tripDeliver.value = Resource.Loading()
try {
val result = withContext(ioDispatcher) {
val tripDeliverParameter = DeliverParameter(
verificationCode,
remark
)
val response = appRepository.setTripDeliver(tripDeliverParameter)
Resource.getResponse { response }
}
_tripDeliver.value = result
} catch (e: Exception) {
when (e) {
is IOException -> _tripDeliver.value =
Resource.Error(messageInt = R.string.no_internet_connection)
else -> _tripDeliver.value =
Resource.Error("Trip Deliver Error: " + e.message)
}
}
}
Logcat
2021-07-09 19:56:10.946 7446-7446/com.package.app I/???: collectTripReattempt loading
2021-07-09 19:56:11.172 7446-7446/com.package.app I/???: collectTripReattempt Success
2021-07-09 19:56:17.703 7446-7446/com.package.app I/???: collectTripReattempt Success
As you can see, the last Success is called again AFTER I pressed back button (popbackstack)
Question
How to make it trigger once only? Is it the way I implement it is wrong? Thank you in advance.
This is not problem of your implementation this is happening because of stateIn() which use used in your viewModel to convert regular flow into stateFlow
If according to your code snippet the success is triggered once again, then why not loading has triggered?
as per article, it is showing the latest cached value when you left the screen and came back you got the latest cached value on view.
Resource:
https://medium.com/androiddevelopers/migrating-from-livedata-to-kotlins-flow-379292f419fb
The latest value will still be cached so that when the user comes back to it, the view will have some data immediately.
I have found the solution, thanks to #Nurseyit Tursunkulov for giving me a clue. I have to use SharedFlow.
At the ViewModel, I replace the initialize with these:
private val _tripDeliver = MutableSharedFlow<Resource<TripDeliverResponse>>(replay = 0)
val tripDeliver: SharedFlow<Resource<TripDeliverResponse>> = _tripDeliver
At the replay I have to use 0, so this SharedFlow will trigger once. Next, change _tripDeliver.value to _tripDeliver.emit() like the codes below:
fun setTripDeliver(
verificationCode: String?,
remark: String?
) = viewModelScope.launch {
_tripDeliver.emit(Resource.Loading())
if (verificationCode == null && remark == null) {
_tripDeliver.emit(Resource.Error("Remark cannot be empty if verification is empty"))
return#launch
}
try {
val result = withContext(ioDispatcher) {
val tripDeliverParameter = DeliverParameter(
verificationCode,
remark,
)
val response = appRepository.setTripDeliver(tripDeliverParameter)
Resource.getResponse { response }
}
_tripDeliver.emit(result)
} catch (e: Exception) {
when (e) {
is IOException -> _tripDeliver.emit(Resource.Error(messageInt = R.string.no_internet_connection))
else -> _tripDeliver.emit(Resource.Error("Trip Deliver Error: " + e.message))
}
}
}
I hope this answer will help the others also.
I think this is because of coldFlow, you need a HotFlow. Another option is to try to hide and show fragment, instead of replacing. And yet another solution is to keep this code in viewModel.
In my opinion, I think your way of using coroutines in lifeScope is incorrect. After the lifeScope status of FragmentA is at Started again, the coroutine will be restarted:
launch {
collectTripDeliver()
}
launch {
collectTripReattempt()
}
So I think: You need to modify this way:
private fun startLifeCycle() {
viewLifecycleOwner.lifecycleScope.launch {
launch {
collectTripDeliver()
}
launch {
collectTripReattempt()
}
}
}
Hello I am doing mvvm project in kotlin and I use room to login and register new user.
Part of code:
view.login_btn.setOnClickListener {
val takenUsername = username.text.toString()
val takenPassword = password.text.toString()
if(takenUsername.isEmpty() || takenPassword.isEmpty()){
Toast.makeText(context, "Fill all columns", Toast.LENGTH_SHORT).show()
}else{
//Zwraca unity (naprawic to a nie null
val userEntity = mMainActivityViewModel.checkLogin(takenUsername,takenPassword)
if(userEntity.equals(null)){
Toast.makeText(context!!, "Bad login or password", Toast.LENGTH_SHORT).show()
}else{
Toast.makeText(context!!, "Login successfull", Toast.LENGTH_SHORT).show()
}
}
}
I dont understand why but this function returns a unit not a null.Which i completly doesnt know.
Could someone propose what should I put instead of null in line 11?
You are following wrong approach my friend. You need to use live data to get the callback from view model.
private fun setupLoginObserver() {
mMainActivityViewModel.loginStatus.observe(this, Observer { isValidUser ->
if (isValidUser) {
Toast.makeText(requireContext(), "Login successful", Toast.LENGTH_SHORT).show()
} else {
Toast.makeText(requireContext(), "Bad login or password", Toast.LENGTH_SHORT).show()
}
})
}
You can call this method from onViewCreated()
Your button click listener should be like:
view.login_btn.setOnClickListener {
val takenUsername = username.text.toString()
val takenPassword = password.text.toString()
if (takenUsername.isEmpty() || takenPassword.isEmpty()) {
Toast.makeText(context, "Fill all columns", Toast.LENGTH_SHORT).show()
} else {
//Check user is valid or not in db and you will get the callback on line #
mMainActivityViewModel.checkLogin(takenUsername, takenPassword)
}
}
ViewModel:
fun checkLogin(username: String, password: String) {
viewModelScope.launch(Dispatchers.IO) {
repository.loginUser(username, password)?.let {
mutableLoginStatus.postValue(true)
} ?: mutableLoginStatus.postValue(false)
}
}
UserRepository:
suspend fun loginUser(username: String, password: String): User? {
return userDao.loginUser(username, password)
}
And Finally UserDao:
#Query("SELECT user_table.* FROM user_table WHERE username= :username AND password=:password")
suspend fun loginUser(username: String, password: String): User?
I have made few required changes in your code and pushed in this branch.
https://github.com/parmeshtoyou/Querto/tree/user_validate_through_live_data_stackoverflow
You can review the changes.
Let me know if you need any clarification.
Happy Coding.
You can use a function to return a boolean, like this
view.login_btn.setOnClickListener {
loginUser()
}
fun loginUser():Boolean{
val takenUsername = username.text.toString()
val takenPassword = password.text.toString()
if(takenUsername.isEmpty() || takenPassword.isEmpty()){
Toast.makeText(context, "Fill all columns", Toast.LENGTH_SHORT).show()
return false
}else{
//Zwraca unity (naprawic to a nie null
val userEntity = mMainActivityViewModel.checkLogin(takenUsername,takenPassword)
if(userEntity.equals(null)){
Toast.makeText(context!!, "Bad login or password", Toast.LENGTH_SHORT).show()
return false
}else{
Toast.makeText(context!!, "Login successfull", Toast.LENGTH_SHORT).show()
return true
}
}
}
}
In MVVM Architecture we can use LiveData, To get the value from ViewModel
In ViewModel, we can validate the Login success or not,
fun checkLogin(username: String, password: String) {
// Perform Login validation
validUser.setValue(true) // set the livedata as true on login validation Success
validUser.setValue(false) // set the livedata as true on login validation falied
}
Inactivity you can Observe the LivedataChanges
mMainActivityViewModel.validUser.observe(this, Observer<Boolean> {validUser:Boolean? ->
if(validUser){
Toast.makeText(this, "Login successfull", Toast.LENGTH_SHORT).show()
}
else{
Toast.makeText(this, "Login Failed", Toast.LENGTH_SHORT).show()
}
})
In OnClickListener
view.login_btn.setOnClickListener {
val takenUsername = username.text.toString()
val takenPassword = password.text.toString()
if(takenUsername.isEmpty() || takenPassword.isEmpty()){
Toast.makeText(context, "Fill all columns", Toast.LENGTH_SHORT).show()
}else{
// just call the method and set the Livedata value based on the validation
mMainActivityViewModel.checkLogin(takenUsername,takenPassword)
}
}