In my Fragment, I'm trying to fetch data from Firebase Database using coroutines where data is retrieving properly. Here is my code
#ExperimentalCoroutinesApi //Fragment Class code
override fun onStart() {
super.onStart()
checkOutViewModel.viewModelScope.launch {
try{
if (isActive){
checkOutViewModel.getCartDataFromFirebaseNetwork().collect{
tempList.add(it)
}
}
}catch (ex : Exception){
Log.d("exception message",ex.cause?.message!!) //Fatal Exception: Main
}
orderListAdapter?.submitList(tempList)
binding.progress.visibility = View.GONE
binding.recycler.visibility = View.VISIBLE
}
}
#ExperimentalCoroutinesApi //Viewmodel class code
suspend fun getCartDataFromFirebaseNetwork()= firebaseNetwork.getCartFromFirebase()
#ExperimentalCoroutinesApi //Repository class code
suspend fun getCartFromFirebase() = callbackFlow<Cart> {
ensureActive()
val counterList = myFlow.toList()
val itemList = myFlow.mapBasketToItemsList().toList()
val pairs = myFlow.mapBasketListToQuantity().toList()
if(itemList.isNotEmpty() && pairs.isNotEmpty()){
for ((current,item) in itemList.withIndex()) {
val cart = Cart(counterList[current].basketId!!,item.id!!,item.url!!,item.name!!,pairs[current].first,pairs[current].second,counterList[current].itemCounter!!,pairs[current].second)
offer(cart)
}
channel.close()
}
}
#ExperimentalCoroutinesApi
val myFlow = callbackFlow<Basket> {
databaseReference.child("Cart").child(getCurrentUserUid())
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
for (data in dataSnapshot.children) {
val basket = Basket()
basket.basketId = data.key
basket.itemId = data.child("itemId").value as String
basket.itemCounter = data.child("itemCounter").value as String
basket.itemWeight = data.child("itemWeight").value as String
offer(basket)
}
channel.close()
}
}
})
awaitClose()
}
#ExperimentalCoroutinesApi
private fun Flow<Basket>.mapBasketToItemsList() : Flow<Items> = map{basket ->
suspendCoroutine<Items> {continuation ->
databaseReference.child("Items").child(basket.itemId!!)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
val items = dataSnapshot.getValue(Items::class.java)!!
continuation.resume(items)
}
}
})
}
}
#ExperimentalCoroutinesApi
private fun Flow<Basket>.mapBasketListToQuantity() : Flow<Pair<String,String>> = map{basket ->
suspendCoroutine<Pair<String,String>> {continuation ->
databaseReference.child("Quantities").child(basket.itemId!!)
.child(basket.itemWeight!!)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
val key = dataSnapshot.key
val value = dataSnapshot.value as String
val myPair = Pair(key!!, value)
continuation.resume(myPair)
}
}
})
}
}
Edited:
This is my Navigation Flow of Fragments
OnBoarding-Authentication-MainFragment-CheckItemListFragment
override fun onStart() { //OnBoarding Fragment
super.onStart()
try {
if(viewModel.checkAuth()){
updateUI()
}
}catch (ex : Exception){
println("In onBoarding Fragment")
Log.d("exception message",ex.cause?.message!!)
}
}
override fun onStart() { //Authentication Fragment
super.onStart()
try {
if(mAuth.currentUser == null){
showShortToast("Please Login")
}else{
updateUI()
}
}catch (ex : Exception){
println("In authentication Fragment")
Log.d("exception message",ex.cause?.message!!)
}
}
override fun onStart() { //MainFragment
super.onStart()
try {
if(mainFragmentViewModel.checkSignIn() == null)
findNavController().navigateUp()
binding.toolbar.add_to_cart.setOnClickListener {
it.findNavController().navigate(R.id.action_mainFragment_to_checkoutItemsList)
}
}catch (ex : Exception){
println("In Main Fragment")
Log.d("exception",ex.message!!)
}
}
#ExperimentalCoroutinesApi
override fun onStart() { //CheckItemList Fragment
super.onStart()
try {
binding.addToCart.setOnClickListener {
checkOutViewModel.viewModelScope.launch {
val message = orderListAdapter?.getList()?.let { it1 -> checkOutViewModel.submitFinalCart(it1) }
if(message!!){
findNavController().navigate(R.id.action_checkoutItemsList_to_finalCarts)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
ensureActive()
checkOutViewModel.getCartDataFromFirebaseNetwork().collect {
tempList.add(it)
orderListAdapter?.submitList(tempList)
binding.progress.visibility = View.GONE
binding.recycler.visibility = View.VISIBLE
}
}
}catch (ex : Exception){
println("In checkItemList Fragment")
Log.d("exception message",ex.cause?.message!!)
}
}
Edited : My Logcat is :-
--------- beginning of crash
07-10 21:18:40.605 30715-30715/com.example.groceryapp E/AndroidRuntime: FATAL
EXCEPTION: main
Process: com.example.groceryapp, PID: 30715
f.d
at com.example.groceryapp.checkout.CheckoutItemsList$e.a(:73)
at f.z.k.a.a.b(:33)
at kotlinx.coroutines.u0.run(:334)
at kotlinx.coroutines.z0.k(:68)
at kotlinx.coroutines.r0.b(:354)
at f.z.i.b(:42)
at com.example.groceryapp.f.a$r$a$b.a(:262)
at com.google.firebase.database.m$a.a(:179)
at com.google.firebase.database.u.a0.a(:75)
at com.google.firebase.database.u.i0.d.a(:63)
at com.google.firebase.database.u.i0.g$a.run(:55)
at android.os.Handler.handleCallback(Handler.java:742)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5603)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:774)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:652)
This is how i used my Try Catch block in every Fragments to handle this issue but it is also not working at all. I'm also using isActive method to check whether the job is still active or not before retrieving any data. I get "Fatal Exception: Main, Job was Cancelled" if i pressed back button before recyclerview shows the data. This exception only comes if i use callback flow. Is there any way to handle this issue or is it a bug in callback flow?. So far I couldn't find any possible answer that will solve my issue. Please tell me how do i fix it?
I faced with the same issue before and here is my solution with an extension of SendChannel
fun <T> SendChannel<T>.offerCatching(element: T): Boolean {
return runCatching { offer(element) }.getOrDefault(false)
}
and when emit event just call offerCatching
Why you launch coroutine in fragment onStart with viewModelScope ?
in fragment/activity you should use lifecycleScope.
see here for more details.
Wrap offer() method into try..catch and capture CancellationException. You may create an extension function to use across the application where using offer() method.
Related
I'm just trying to find an answer how to pass the data from Repository to ViewModel without extra dependencies like RxJava. The LiveData seems as a not good solution here because I don't need to proceed it in my Presentation, only in ViewModel and it's not a good practice to use observeForever.
The code is simple: I use Firebase example trying to pass data with Flow but can't use it within a listener (Suspension functions can be called only within coroutine body error):
Repository
fun fetchFirebaseFlow(): Flow<List<MyData>?> = flow {
var ret: List<MyData>? = null
firebaseDb.child("data").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val data = dataSnapshot.getValue<List<MyData>>()
emit(data) // Error. How to return the data here?
}
override fun onCancelled(databaseError: DatabaseError) {
emit(databaseError) // Error. How to return the data here?
}
})
// emit(ret) // Useless here
}
ViewModel
private suspend fun fetchFirebase() {
repo.fetchFirebaseFlow().collect { data ->
if (!data.isNullOrEmpty()) {
// Add data to something
} else {
// Something else
}
}
You can use callbackFlow
#ExperimentalCoroutinesApi
fun fetchFirebaseFlow(): Flow<List<String>?> = callbackFlow {
val listener = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
val data = dataSnapshot.getValue<List<MyData>>()
offer(data)
}
override fun onCancelled(databaseError: DatabaseError) {
}
}
val ref =firebaseDb.child("data")
reef.addListenerForSingleValueEvent(listener)
awaitClose{
//remove listener here
ref.removeEventListener(listener)
}
}
ObservableField is like LiveData but not lifecycle-aware and may be used instead of creating an Observable object.
{
val data = repo.getObservable()
val cb = object : Observable.OnPropertyChangedCallback() {
override fun onPropertyChanged(observable: Observable, i: Int) {
observable.removeOnPropertyChangedCallback(this)
val neededData = (observable as ObservableField<*>).get()
}
}
data.addOnPropertyChangedCallback(cb)
}
fun getObservable(): ObservableField<List<MyData>> {
val ret = ObservableField<List<MyData>>()
firebaseDb.child("events").addListenerForSingleValueEvent(
object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
ret.set(dataSnapshot.getValue<List<MyData>>())
}
override fun onCancelled(databaseError: DatabaseError) {
ret.set(null)
}
})
return ret
}
It is also possible to use suspendCancellableCoroutine for a single result. Thanks to Kotlin forum.
my data is fetched only when it is created...im using viewmodel...when press back button it doesnt update the previous data..onresume is not working in this...
i refered this but none of those helped--> Reacting to activity lifecycle in ViewModel
i need help
thanks in advance
activity:--
class MyAccount : BaseClassActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.myaccount)
var mActionBarToolbar = findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbartable);
setSupportActionBar(mActionBarToolbar);
setEnabledTitle()
val resetbutton=findViewById<Button>(R.id.resetpwd)
resetbutton.setOnClickListener {
val i=Intent(applicationContext,
ResetPasswordActivity::class.java)
startActivity(i)
}
val editbutton=findViewById<Button>(R.id.editdetail)
editbutton.setOnClickListener {
val i=Intent(applicationContext, EditProfile::class.java)
startActivity(i)
}
hello()
}
override fun onResume() {
super.onResume()
hello()
}
fun hello(){
val first_name = findViewById<TextView>(R.id.firstname)
val last_name = findViewById<TextView>(R.id.lastname)
val emailuser = findViewById<TextView>(R.id.emailuser)
val phone_no = findViewById<TextView>(R.id.phone_no)
val birthday = findViewById<TextView>(R.id.birthday)
val image=findViewById<ImageView>(R.id.imageprofile)
val model = ViewModelProvider(this)[MyAccountViewModel::class.java]
model.viewmodel?.observe(this, object : Observer<My_account_base_response> {
override fun onChanged(t: My_account_base_response?) {
first_name.setText(t?.data?.user_data?.first_name)
last_name.setText(t?.data?.user_data?.last_name)
emailuser.setText(t?.data?.user_data?.email)
phone_no.setText(t?.data?.user_data?.phone_no).toString()
birthday.setText(t?.data?.user_data?.dob).toString()
Glide.with(applicationContext).load(t?.data?.user_data?.profile_pic)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.ic_launcher_foreground)
.into(image)
}
})
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
NavUtils.navigateUpFromSameTask(this)
true
}
else -> super.onOptionsItemSelected(item)
}
}}
viewmodel:--
class MyAccountViewModel(context: Application) :AndroidViewModel(context),LifecycleObserver{
private var MyAccountViewModels: MutableLiveData<My_account_base_response>? = null
val viewmodel: MutableLiveData<My_account_base_response>?
get() {
if (MyAccountViewModels == null) {
MyAccountViewModels = MutableLiveData<My_account_base_response>()
loadviewmodel()
}
return MyAccountViewModels
}
private fun loadviewmodel(){
val token :String = SharedPrefManager.getInstance(getApplication()).user.access_token.toString()
RetrofitClient.instance.fetchUser(token)
.enqueue(object : Callback<My_account_base_response> {
override fun onFailure(call: Call<My_account_base_response>, t: Throwable) {
Log.d("res", "" + t)
}
override fun onResponse(
call: Call<My_account_base_response>,
response: Response<My_account_base_response>
) {
var res = response
if (res.body()?.status == 200) {
MyAccountViewModels!!.value = response.body()
} else {
try {
val jObjError =
JSONObject(response.errorBody()!!.string())
Toast.makeText(getApplication(),
jObjError.getString("user_msg"),
Toast.LENGTH_LONG).show()
} catch (e: Exception) {
Log.e("errorrr", e.message)
}
}
}
})
}}
There are bunch of things wrong here, so let me provide you refactored code and explanation as much as I would be able to..
Activity:
class MyAccount : BaseClassActivity() {
private val mActionBarToolbar by lazy { findViewById<androidx.appcompat.widget.Toolbar>(R.id.toolbartable) }
private val resetbutton by lazy { findViewById<Button>(R.id.resetpwd) }
private val editbutton by lazy { findViewById<Button>(R.id.editdetail) }
private val first_name by lazy { findViewById<TextView>(R.id.firstname) }
private val last_name by lazy { findViewById<TextView>(R.id.lastname) }
private val emailuser by lazy { findViewById<TextView>(R.id.emailuser) }
private val phone_no by lazy { findViewById<TextView>(R.id.phone_no) }
private val birthday by lazy { findViewById<TextView>(R.id.birthday) }
private val image by lazy { findViewById<ImageView>(R.id.imageprofile) }
lateinit var model: MyAccountViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.myaccount)
setSupportActionBar(mActionBarToolbar)
setEnabledTitle()
model = ViewModelProvider(this)[MyAccountViewModel::class.java]
resetbutton.setOnClickListener {
val i = Intent(applicationContext, ResetPasswordActivity::class.java)
startActivity(i)
}
editbutton.setOnClickListener {
val i = Intent(applicationContext, EditProfile::class.java)
startActivity(i)
}
model.accountResponseData.observe(this, object : Observer<My_account_base_response> {
override fun onChanged(t: My_account_base_response?) {
first_name.setText(t?.data?.user_data?.first_name)
last_name.setText(t?.data?.user_data?.last_name)
emailuser.setText(t?.data?.user_data?.email)
phone_no.setText(t?.data?.user_data?.phone_no).toString()
birthday.setText(t?.data?.user_data?.dob).toString()
Glide.with(applicationContext)
.load(t?.data?.user_data?.profile_pic)
.diskCacheStrategy(DiskCacheStrategy.ALL)
.placeholder(R.drawable.ic_launcher_foreground)
.into(image)
}
})
}
override fun onResume() {
super.onResume()
model.loadAccountData()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
android.R.id.home -> {
NavUtils.navigateUpFromSameTask(this)
true
}
else -> super.onOptionsItemSelected(item)
}
}
}
Few notes on your activity class:
You don't need to findViewById everytime, just do it once during onCreate or do it lazily. (FYI consider using kotlin synthetics or view binding or data binding)
Initialize your viewModel during onCreate method only. (That's the best way to do it)
Also observer your LiveData from ViewModel once, it should be also from the onCreate as it's the entry point to the activity and apart from config changes this method called only once. So, it's safe to observe it over there rather than during onResume which will be called multiple times during activity lifecycle. (The main issue your code wasn't working, so as a fix you only call your API method from ViewModel during resume)
ViewModel:
class MyAccountViewModel(context: Application) : AndroidViewModel(context) {
private val _accountResponseData = MutableLiveData<My_account_base_response?>()
val accountResponseData: MutableLiveData<My_account_base_response?>
get() = _accountResponseData
init {
loadAccountData()
}
fun loadAccountData() {
val token: String = SharedPrefManager.getInstance(getApplication()).user.access_token.toString()
RetrofitClient.instance.fetchUser(token)
.enqueue(object : Callback<My_account_base_response> {
override fun onFailure(call: Call<My_account_base_response>, t: Throwable) {
Log.d("res", "" + t)
_accountResponseData.value = null
}
override fun onResponse(
call: Call<My_account_base_response>,
response: Response<My_account_base_response>
) {
var res = response
if (res.body()?.status == 200) {
_accountResponseData.value = response.body()
} else {
try {
val jObjError =
JSONObject(response.errorBody()!!.string())
Toast.makeText(
getApplication(),
jObjError.getString("user_msg"),
Toast.LENGTH_LONG
).show()
} catch (e: Exception) {
Log.e("errorrr", e.message)
}
}
}
})
}
}
Don't make initial API call along with LiveData creation, it's okay to do in most of cases but if you're updating LiveData on response of that call then it's good to make it separately like during init block.
It's good practice not to allow Ui (Activity/Fragments) to modify LiveDatas of ViewModel directly. So, that's good sign you're following such pattern by having private MutableLiveData exposed as public LiveData, but do it correctly as suggested.
Side note: Your view model doesn't need to be LifecycleObserver. LifecycleObserver is used for some custom class/component which needs to be managed by their self by silently observing/depending on activity lifecycle independently. That's not the use case of ViewModel.
The only thing that I found why your code wasn't working correctly is because you were creating & observing ViewModel & LiveData over & over again as new objects from onResume method where you called hello() method.
Let me know if something don't make sense or missing.
I want to implement firebase realtime database with coroutines, so I need to use flow because firebase just accept callbacks. the problem is the .collect{} block never gets executed
here is my code
#ExperimentalCoroutinesApi
override suspend fun getProduct(barcode: String): ProductItem? {
return withContext(Dispatchers.Default) {
println("Hi")
var item: ProductItem? = null
productFlow(barcode).collect {
//this never gets called
print("Getting product")
item = it
}
println("Ending product request ${item?.name}")
Log.i("GetProduct",item?.name)
item
}
}
#ExperimentalCoroutinesApi
private fun productFlow(barcode: String): Flow<ProductItem?> = callbackFlow {
val database = FirebaseDatabase.getInstance()
val productRef = database.getReference("products/$barcode")
val callback = object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
for(snapshot in dataSnapshot.children){
Log.i("Source", snapshot.value.toString())
}
val product = dataSnapshot.getValue(ProductItem::class.java)
Log.i("Source",product?.name) //everything is good until here
sendBlocking(dataSnapshot.getValue(ProductItem::class.java)) //after this i dont get anything on the collect{} block
}
override fun onCancelled(databaseError: DatabaseError) {
println("cancelling")
sendBlocking(null)
}
}
try {
productRef.addListenerForSingleValueEvent(callback)
} catch (e: FirebaseException) {
println("Firebase exception")
sendBlocking(null)
}
awaitClose{
println("Closing")
productRef.removeEventListener(callback)
}
}
First I would suggest to use the catch method to check if there is an error or not. Second, for callbackflow I remember using offer() instead of sendBlocking
the following snippet returns the result as 'null' on sequential code flow. I understand coroutines could be a viable solution to handle the callback asynchronously.
fun getUserProperty(path: String): String? {
var result: String? = null
database.child(KEY_USERS).child(getUid()).child(path)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(error: DatabaseError) {
Log.e(TAG, "error: $error")
}
override fun onDataChange(snapshot: DataSnapshot) {
Log.w(TAG, "value: ${snapshot.value}")
result = snapshot.value.toString()
}
})
return result
}
Can the coroutines be of any help in this scenario to wait until the result of the callbacks (onDataChange()/onCancelled())?
Since the Firebase Realtime Database SDK doesn't provide any suspend functions, coroutines are not helpful when dealing with its APIs. You would need to convert the callback into a suspend function in order for you to be able to await the result in a coroutine.
Here's a suspend extension function that does this (I discovered a solution it by doing a google search):
suspend fun DatabaseReference.getValue(): DataSnapshot {
return async(CommonPool) {
suspendCoroutine<DataSnapshot> { continuation ->
addListenerForSingleValueEvent(FValueEventListener(
onDataChange = { continuation.resume(it) },
onError = { continuation.resumeWithException(it.toException()) }
))
}
}.await()
}
class FValueEventListener(val onDataChange: (DataSnapshot) -> Unit, val onError: (DatabaseError) -> Unit) : ValueEventListener {
override fun onDataChange(data: DataSnapshot) = onDataChange.invoke(data)
override fun onCancelled(error: DatabaseError) = onError.invoke(error)
}
With this, you now how a getValue() suspect method on DatabaseReference that can be awaited in a coroutine.
The #Doug example for singleValueEvent if you want to keep listing you can use coroutine flow like below:
#ExperimentalCoroutinesApi
inline fun <reified T> DatabaseReference.listen(): Flow<DataResult<T?>> =
callbackFlow {
val valueListener = object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
close(databaseError.toException())
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
try {
val value = dataSnapshot.getValue(T::class.java)
offer(DataResult.Success(value))
} catch (exp: Exception) {
Timber.e(exp)
if (!isClosedForSend) offer(DataResult.Error(exp))
}
}
}
addValueEventListener(valueListener)
awaitClose { removeEventListener(valueListener) }
}
In case anyone still uses the original answer's code but needs to update it to match the non-experimental version of Coroutines here's how I changed it:
import com.google.firebase.database.DataSnapshot
import com.google.firebase.database.DatabaseError
import com.google.firebase.database.DatabaseReference
import com.google.firebase.database.ValueEventListener
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
import kotlin.coroutines.suspendCoroutine
suspend fun DatabaseReference.getSnapshotValue(): DataSnapshot {
return withContext(Dispatchers.IO) {
suspendCoroutine<DataSnapshot> { continuation ->
addListenerForSingleValueEvent(FValueEventListener(
onDataChange = { continuation.resume(it) },
onError = { continuation.resumeWithException(it.toException()) }
))
}
}
}
class FValueEventListener(val onDataChange: (DataSnapshot) -> Unit, val onError: (DatabaseError) -> Unit) : ValueEventListener {
override fun onDataChange(data: DataSnapshot) = onDataChange.invoke(data)
override fun onCancelled(error: DatabaseError) = onError.invoke(error)
}
Then using it would be as simple as: val snapshot = ref.getSnapshotValue()
Update
I also needed to observe a node and used Omar's answer to do it. If anyone needs an example of how to use it here it is:
#ExperimentalCoroutinesApi
inline fun <reified T> DatabaseReference.listen(): Flow<T?>? =
callbackFlow {
val valueListener = object : ValueEventListener {
override fun onCancelled(databaseError: DatabaseError) {
close()
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
try {
val value = dataSnapshot.getValue(T::class.java)
offer(value)
} catch (exp: Exception) {
if (!isClosedForSend) offer(null)
}
}
}
addValueEventListener(valueListener)
awaitClose { removeEventListener(valueListener) }
}
Then to call it inside an Activity or Fragment you would create your listener like so:
var listener = FirebaseUtils.databaseReference
.child(AppConstants.FIREBASE_PATH_EMPLOYEES)
.child(AuthUtils.retrieveUID()!!).listen<User>()
Then call it inside your function:
CoroutineScope(IO).launch {
withContext(IO) {
listener?.collect{
print(it)
}
}
}
And then dispose inside onStop():
override fun onStop(){
listener = null
super.onStop()
}
I am able to authenticate user by Firebase authentication but could not able to store data in Firebase database. I am not getting any error in logcat. I haven't changed any rules in Firebase database. My rules are set default. Here is my code:
fun authenticateUser(){
Observable.create(ObservableOnSubscribe<Task<AuthResult>>{
e: ObservableEmitter<Task<AuthResult>> -> run {
try {
firebaseAuth.createUserWithEmailAndPassword(email!!, password!!)
.addOnCompleteListener(this, object : OnCompleteListener<AuthResult> {
override fun onComplete(p0: Task<AuthResult>) {
e.onNext(p0)
e.onComplete()
}
})
}
catch (ex : Exception){
dialogs?.dismiss()
displayMessage("network problem..")
e.onError(ex)
}
}
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Observer<Task<AuthResult>>{
override fun onError(e: Throwable) {
e.printStackTrace()
}
override fun onNext(t: Task<AuthResult>) {
if (t.isSuccessful) {
storeData()
}
else if (!t.isSuccessful){
dialogs?.dismiss()
displayMessage("some issues has came..")
}
}
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
compositeDisposable?.add(d)
}
})
}
override fun storeData() {
var currentUser = firebaseAuth.currentUser
val databaseReference = firebaseDatabase.reference.child("Users_Information").child(currentUser?.uid)
val data = HashMap<String,String>()
data.put("name",nameString!!)
data.put("email_address",emailString!!)
data.put("image","def")
data.put("thumb_image","def")
databaseReference.setValue(data).addOnCompleteListener {
task -> if (task.isSuccessful){
dialogs?.dismiss()
displayMessage("user is created")
goToNextActivity()
}
else if (!task.isSuccessful){
dialogs?.dismiss()
displayMessage("Authentication failed, try again later")
}
else if (task.isComplete){
displayMessage("data not stored")
}
}
}
override fun goToNextActivity() {
var intents = Intent(this,MainActivity::class.java)
intents.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
intents.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
startActivity(intents)
finish()
}
Uid is generating but data is not storing in Firebase database.I debug the code but didn't find any issue. Where is the problem, please help...
Try to send Firebaseuser to storeData() and try to log Firebaseuser in storeData()
override fun onNext(t: Task<AuthResult>) {
if (t.isSuccessful) {
Log.d("FB user", t.getResult().getUser())
storeData(t.getResult().getUser())
}
else if (!t.isSuccessful){
dialogs?.dismiss()
displayMessage("some issues has came..")
}
}
....
..
override fun storeData(curentUser: Firebaseuser) {
Log.d("FB user", currentUser?.uid)
val databaseReference = firebaseDatabase.reference.child("Users_Information").child(currentUser?.uid)
...
..
}