Why Firebase Task in RxJava Completable emitter doesn't execute? - firebase

I'm developing a Firebase Android application which connect to a Firestore. The nomenclature is that the collection is "Assets". The example code had simple actions like addAsset and deleteAsset, those work fine. This is the data repository layer which actually converse with Firebase, the view model layer is above this.
class FirestoreAssetRepository(secondaryDB: FirebaseFirestore) : IAssetRepository {
companion object {
private const val TAG = "FirestoreAssetRepo"
private const val ASSET_COLLECTION = "Assets"
}
private var remoteDB: FirebaseFirestore
private var changeObservable: Observable<List<DocumentSnapshot>>
init {
remoteDB = secondaryDB
}
override fun addAsset(asset: Asset): Completable {
return Completable.create { emitter ->
remoteDB.collection(ASSET_COLLECTION)
.add(mapToAssetData(asset))
.addOnSuccessListener {
if (!emitter.isDisposed) {
emitter.onComplete()
}
}
.addOnFailureListener {
if (!emitter.isDisposed) {
emitter.onError(it)
}
}
}
}
override fun deleteAsset(assetId: String): Completable {
return Completable.create { emitter ->
remoteDB.collection(ASSET_COLLECTION)
.document(assetId)
.delete()
.addOnSuccessListener {
if (!emitter.isDisposed) {
emitter.onComplete()
}
}
.addOnFailureListener {
if (!emitter.isDisposed) {
emitter.onError(it)
}
}
}
}
I'm adding an action to the repository which would modify a specific document.
override fun lockUnlockAsset(assetId: String): Completable {
Log.d(TAG, "lockUnlockAsset")
return Completable.create { emitter ->
remoteDB.collection(ASSET_COLLECTION)
.document(assetId)
.get()
.addOnSuccessListener {
Log.d(TAG, "Unlocking")
val remoteAsset = mapDocumentToRemoteAsset(it)
it.reference.update(getUnlockLocation())
if (!emitter.isDisposed) {
emitter.onComplete()
}
}
.addOnFailureListener {
Log.d(TAG, "Could not find asset to unlock")
if (!emitter.isDisposed) {
emitter.onError(it)
}
}
}
}
The execution reaches Log.d(TAG, "lockUnlockAsset") but never gets to Log.d(TAG, "Unlocking"). If I place a break point at that second logging command it is the usual red dot in the beginning, but when the call comes into the function the icon changes to a grey "don't enter" icon and when I hover over it Android Studio tells me that "No executable found at ...". So something is definitely wrong there.
I'm new to Kotlin and RxJava2. How can I get this to work?
Update: to answer Pavel's question: these functions are called from the ViewModel layer:
fun deleteAsset(assetId: String) {
repository.deleteAsset(assetId)
.subscribeOn(Schedulers.io())
.subscribe(
{},
{
it.printStackTrace()
})
.addTo(disposable)
}
fun addAsset(assetTitle: String) {
repository.addAsset(Asset("${System.currentTimeMillis()}", assetTitle))
.subscribeOn(Schedulers.io())
.subscribe(
{},
{
it.printStackTrace()
})
.addTo(disposable)
}
fun lockUnlockAsset(assetId: String) {
repository.lockUnlockAsset(assetId)
}
I was experimenting with combinations of .subscribeOn(Schedulers.io()).observe at the repository level. Maybe it's the .addTo(disposable) which got it working, I'm not sure what I was missing. Now it's working, I wait for Pavel for his answer.

I experimented with combinations of .subscribeOn(...) and observeOn(..) + .observe(...) at the data repository level, but I should have just followed the pattern in the view model (view model calls the functions of the data repository): it's a chained subscribeOn + subscribe + addTo(disposable):
fun lockUnlockAsset(assetId: String) {
repository.lockUnlockAsset(assetId)
.subscribeOn(Schedulers.io())
.subscribe(
{},
{
it.printStackTrace()
})
.addTo(disposable)
}
Thanks for Pavel for pointing this out.

Related

Using RxList variable and trigger other RxList variable on GetxController streamBinding() with firebase

I am using Firebase Firestore. and also
GetxController, RxList, Streaming
First of all, I would like to describe my scenario roughly.
In my scenario I am adding or removing the Clubs array in the player collection. This change I made is correctly reflected in the AccountController.clubs array variable. but the list from the readClubs method, where I pulled the details of the club information, is not updated. I am using the AccountController.clubs array list in the where condition in the readClubs() method. Although the array list is updated, the _clubs RxList variable and ClubController.clubs object list, where I keep the detailed club list, are not updated. I can see the correct values only when I restart the application
I have shared the relevant branches below. I haven't shared the view codes because I can't see the correct values in the variables yet.
Data Model
//Players:
[
{
"playerUid":"1",
"Name":"Ramazan",
"Clubs":["1","2"]
}
]
//Clubs:
[
{
"clubUid":"1",
"Name":"Club1"
},
{
"clubUid":"2",
"Name":"Club2",
},
{
"clubUid":"3",
"Name":"Club3"
}
]
AccountController
onInit() async {
super.onInit();
_loggedAccount.bindStream(Database.initAccount(uid));
}
final Rx<AccountModel> _loggedAccount = AccountModel().obs;
List<String> get clubs => _getPlayerCLubsUids();
AccountModel get loggedAccount => _loggedAccount.value;
List<String> _getPlayerCLubsUids() {
List<String> clubUids = [];
if (loggedAccount.clubs != null) {
for (var e in loggedAccount.clubs!) {
clubUids.add(e.uid);
}
}
return clubUids;
}
ClubController
final RxList<ClubModel> _clubs = <ClubModel>[].obs;
List<ClubModel> get clubs => _getCLubs();
#override
void onInit() {
super.onInit();
_clubs.bindStream(Database.readClubs());
}
Database
static Stream<List<ClubModel>> readClubs() {
if (AccountController.to.clubs.isNotEmpty) {
return _firestore
.collection('clubs')
.where('uid', whereIn: AccountController.to.clubs)
.snapshots()
.map((QuerySnapshot query) {
List<ClubModel> retVal = [];
for (var element in query.docs) {
retVal.add(ClubModel.fromSnapshot(element));
}
return retVal;
});
} else {
return const Stream.empty();
}
}
The part I think is causing the problem. in Database.read Clubs() because I have same matzot without where condition.
.where('uid', whereIn: AccountController.to.clubs)

Completion handler for Firebase Realtime DB lookup when one function needs the value of a previous function

I have the following code that fetches a schedule
func fetchSchedule(completion: #escaping () -> ()) {
scheduleRef.queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { snapshot in
self.schedule = []
if snapshot.value is NSNull {
// Null
} else {
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let schedule = Schedule(snapshot: snapshot) {
self.schedule.append(schedule)
}
}
}
})
}
The above get the current schedule but what I am unclear on is that i need that value to then call the next function call which get the associated games for that schedule on the .onAppear() of the view in SwiftUI
func getGames() {
scheduleStore.fetchSchedule()
//
gameStore.fetchGames(weekId: self.scheduleStore.schedule[0].weekId)
}
the gameStore.fetchGames always returns null, likely because it has not finished processing the fetchSchedule function?
How do I ensure the first function finishes before it calls the fetchGames?
You have a completion handler built into your function signature on fetchSchedule, but you aren't using it.
func fetchSchedule(completion: #escaping () -> ()) {
scheduleRef.queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { snapshot in
self.schedule = []
if snapshot.value is NSNull {
// Null
} else {
for child in snapshot.children {
if let snapshot = child as? DataSnapshot,
let schedule = Schedule(snapshot: snapshot) {
self.schedule.append(schedule)
}
}
completion() //<-- Here
}
})
}
Then,
func getGames() {
scheduleStore.fetchSchedule(completion: {
gameStore.fetchGames(weekId: self.scheduleStore.schedule[0].weekId)
})
}
You're not showing all of your code, but you may also have something broken between self.schedule, which you set in fetchSchedule, and self.scheduleStore, you you send to fetchGames -- make sure you've only got one place you're storing data -- should it be self.schedule in both places?
Update, based on comments
This code is approximate, since I don't have access to your types, but it should get you started:
func fetchSchedule(completion: #escaping ([Schedule]) -> ()) {
scheduleRef.queryOrderedByValue().queryEqual(toValue: true).observe(.value, with: { snapshot in
if snapshot.value is NSNull {
// Null
} else {
let schedules = snapshot.children.compactMap { child in
if let snapshot = child as? DataSnapshot, let schedule = Schedule(snapshot: snapshot) {
return schedule
}
return nil
}
completion(schedules)
}
})
}
func getGames() {
scheduleStore.fetchSchedule { schedules in
gameStore.fetchGames(weekId: schedules[0].weekId)
}
}

How do I correct this issue with reading an Environment Object? (SwiftUI and working without a SceneDelegate)

I wanted to write a template for apps that logs into firebase so that I can use it in future projects. I followed a tutorial online found on YouTube :
https://www.youtube.com/watch?v=DotGrYBfCuQ&list=PLBn01m5Vbs4B79bOmI3FL_MFxjXVuDrma&index=2
So the issue I'm facing is that in the video the variable userInfo was instantiated in the SceneDelegate, allowing the coder on YouTube to reference userInfo in his code. I tried doing the same in the AppDelegate and the App Struct. with no avail.
Here is the code in the App Struct:
import SwiftUI
import Firebase
#main
struct WoobApp: App {
// Adapts AppDelegate to SwiftUI
#UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var userInfo = UserInfo()
var body: some Scene {
WindowGroup {
InitialView()
}
}
}
class AppDelegate : NSObject, UIApplicationDelegate {
// Configure Firebase When App Launches
func application(_ application : UIApplication, didFinishLaunchingWithOptions launchOptions : [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
FirebaseApp.configure()
return true
}
}
I think the issue is in here, however I will post the rest of my code in case Im wrong:
InitialView:
struct InitialView: View {
#EnvironmentObject var userInfo : UserInfo
var body: some View {
Group {
if userInfo.isUserAuthenticated == .undefined {
UndefinedView()
}
else if userInfo.isUserAuthenticated == .signedOut {
UndefinedView()
}
else if userInfo.isUserAuthenticated == .signedIn {
UndefinedView()
}
}
.onAppear{
self.userInfo.configureFirebaseStateDidChange()
}
}
And here is the User data :
class UserInfo : ObservableObject {
enum FBAuthState {
case undefined, signedIn, signedOut
}
#Published var isUserAuthenticated : FBAuthState = .undefined
func configureFirebaseStateDidChange() {
isUserAuthenticated = .signedIn
isUserAuthenticated = .signedOut
}
}
Thanks in advance for any help, Id really appreciate it so thank you!!!!
You have to actually pass that userInfo variable into your view hierarchy so that it's visible to InitialView and its children:
#ObservedObject var userInfo = UserInfo()
var body: some Scene {
WindowGroup {
InitialView()
.environmentObject(userInfo)
}
}
This principal of passing it via environmentObject is true whether you're using the SwiftUI lifecycle or a SceneDelegate. More reading on environmentObject: https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views
You have a choice of whether to declare userInfo as an ObservedObject or StateObject that may depend on your OS target version: What is the difference between ObservedObject and StateObject in SwiftUI

Set UI elements after read firebase kotlin

I am trying to read a document from a database in firebase and show the info at the UI. Any idea why is it not working?
Viewmodel:
var leid = MediatorLiveData<Gas>()
init {
initializeDocument()
}
fun initializeDocument(){
mFirestore?.collection("expenditures")?.document("ASfavaftaseacdadf")?.get()?.addOnSuccessListener { document ->
if (document != null) {
Log.d("TAG", "DocumentSnapshot data: ${document.data}")
var gastl = document.toObject<Gas>()!!
leid.value=gastl
} else {
Log.d("TAG", "No such document")
}
}
?.addOnFailureListener { exception ->
Log.d("TAG", "get failed with ", exception)
}
}
After that, I use databinding for the UI. When I try to execute the code, I dont get any log or anything; It is like the function never execute.
Thanks in advance.

Trigger an alert when using Firebase observer and events

Working on an app that has two parts - Rider & Driver. When the driver accepts the request, an alert is then sent to the rider that the request was accepted and driver is on the way.
Unable to trigger the alert to the rider.
RiderVC:
func driverAcceptedRequest(requestAccepted: Bool, driverName: String) {
if !riderCancelledRequest {
if requestAccepted {
self.alertTheUser(title: "Ryde Accepted", message: "\(driverName) Has Accepted Your Ryde Request and will message you with details")
} else {
RydeHandler.Instance.cancelRyde()
alertTheUser(title: "Ryde Cancelled", message: "\(driverName) Has Cancelled the Ryde Request")
}
}
riderCancelledRequest = false
}
RydeHandler.swift:
// DRIVER ACCEPTED RYDE
DataService.Instance.requestAcceptedRef.observe(FIRDataEventType.childAdded) { (snapshot: FIRDataSnapshot) in
if let data = snapshot.value as? NSDictionary {
if let name = data[Constants.NAME] as? String {
if self.driver == "" {
self.driver = name
self.delegate?.driverAcceptedRequest(requestAccepted: true, driverName: self.driver)
}
}
}
}
Firebase database structure:
Edit
ViewDidLoad in tableviewcontroller - list of requests:
ref.child("drivers").child("RideRequests").observe(FIRDataEventType.value, with: { snapshot in
self.rideRequests.removeAll()
for item in snapshot.children{
self.rideRequests.append(item as! FIRDataSnapshot)
}
self.rideRequests.reverse()
self.tableView.reloadData()
})

Resources