RxSwift: Completable concat in a deferred way - swift

Can anybody tell me if it is possible to create a deferred completable in a concat operator.
I want to fetch a session, and after this load a user with the corresponding session id.
SessionAPI.post(email: email, password: password)
UserAPI.get(id: Session.load()!.userId)
Until now I used observables with the flatMap operator.
I will now try to reproduce the same behaviour with the completables, which doesn't have flatMap operator.
Working code with observables:
SessionAPI.post(email: email, password: password)
.flatMap { (_) -> Single<Any> in
return UserAPI.get(id: Session.load()!.userId)
}
New working code with completables
SessionAPI.post(email: email, password: password)
.concat(Completable.deferred { UserAPI.get(id: Session.load()!.userId) } )
I now want to create an extension for this deferred completable, like:
SessionAPI.post(email: email, password: password)
.concatDeferred(UserAPI.get(id: Session.load()!.userId))
Current extension:
extension PrimitiveSequenceType where Self.Element == Never, Self.Trait == RxSwift.CompletableTrait {
func concatDeferred(_ second: RxSwift.Completable) -> RxSwift.Completable {
return Completable.deferred { () -> PrimitiveSequence<CompletableTrait, Never> in
return second
}
}
}
Issue: The Session.load()! in UserAPI.get is loaded and crashing before SessionAPI.post finished.
Does someone got an idea to get this extension up running?
Thanks!

I'm going to assume that the reason you want to defer your UserAPI.get(id:) is because some "magic" is happening in the background where SessionAPI.post(email:password:) is making it so Session.load() is valid.
What this tells me is that the post(email:password:) should not be completable in the first place. Rather it should return an Observable<T> where T is whatever Session.load() returns.
You can't make the code work like you want:
SessionAPI.post(email: email, password: password)
.concatDeferred(UserAPI.get(id: Session.load()!.userId))
With the above code, Session.load() will get called before SessionAPI.post(email:password:) is even called no matter what code you put in concatDeferred.
The Session.load() function must be called before concatDeferred is so that the former can pass its result into the latter.

Related

How to test Function passed as an argument in Flutter?

How to test Function passed as an argument in Flutter?
code:
Future<User> execute({
required String username,
required String password,
required void Function(AuthFailure fail) onFailure,
required void Function(User user) onSuccess,
}) async {
if (username.isNonValid || password.isNonValid) {
onFailure(const AuthFailure.wrongCredentials()); // I want to test this line
return const User.anonymous();
}
...
}
test:
test('use case - failure execution for incorrect credentials', () async {
// GIVEN
// WHEN
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) {},
onSuccess: (user) {},
);
// THEN
// TODO how to verify onFailure call inside useCase?
expect(user, const User.anonymous());
});
Or maybe testing this way is not the idiomatic way, because the test becomes more white-box instead black-box? Should I perceive passing functions as arguments to use cases as anti-pattern? I can change it then. The proposition is to return sth like Either from useCase.execute():
Future<Either<Failure, Success>> execute({
required String username,
required String password,
}) async {
if (username.isEmpty || password.isEmpty) {
// return wrapper around AuthFailure.wrongCredentials()) of Either left subtype (Either has two subtypes)
}
...
}
This way I only verify return type, and all the lines are covered this way. It's gonna work, but I feel better with the simplest, not the smartest solution.
PS I use Mocktail for mocking, but using Mockito in solution is also warmly welcomed.
If you just want to verify that the callback is triggered, I personally would just make your callback set a flag and then test that flag afterward, which I think is straightforward, simple, and easy to understand with no magic:
test('use case - failure execution for incorrect credentials', () async {
var failureCalled = false;
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) => failureCalled = true,
onSuccess: (user) {},
);
expect(user, const User.anonymous());
expect(failureCalled, true);
});
But if you really want to use Mocks, you will need some Mock object to use and to call instance methods on that in callbacks. With Mockito you could do:
test('use case - failure execution for incorrect credentials', () async {
dynamic mock = Mock();
final user = await useCase.execute(
username: "noname",
password: "password",
onFailure: (fail) => mock.fail(fail),
onSuccess: (user) {},
);
expect(user, const User.anonymous());
verify(mock.fail(any)).called(1);
});
Some things to note:
To avoid declaring a class with the expected instance methods and then code-generating stubs, create a raw Mock instance but declare it as dynamic to disable static type-checking. This will then take advantage the Mock.noSuchMethod implementation.
You can't use onFailure: mock.fail directly since the Mock has no generated stubs, and mock.fail will just be null instead of a Function.
I am not experienced with Mocktail, but I imagine that you could do something similar.

Create an account and a document in Firestore at the same time?

I'm using Flutter and Firebase for my app and the following is the code for my register function:
Future registerWithEmailAndPassword(String email, String name, String password) async {
try{
// Creates user account with Firebase Auth:
UserCredential result = await _auth.createUserWithEmailAndPassword(email: email, password: password);
User user = result.user!;
// Creates a new document in Firestore with the uid:
await DatabaseService(uid: user.uid).createUserData(
name: name,
email: email,
);
return _userObjectFromUser(user);
} on FirebaseAuthException catch(e) {
return e;
}
}
It works well. However, I keep wondering if this is the best way to do this... What if the connection gets interrupted after creating the account but before creating the documents in Firestore? What if the creation of the document fails for some reason? Then the user would be in a weird situation where they have an account but no data saved in the database, meaning the app would probably load forever.
So, I wonder: is there a way to create something similar to a batch write that would somehow create an account at the same time as the documents are created?
I guess you shouldn't be concerned about this since the two methods will run on each other, they're in a really small chance of this happening, either both will succeed or both will fail together, however, I can recommend for those cases to listen to the authStateChanges() stream and take an action based on it, combined with using the isNew like this :
// At first, we were not sure that the document exists
bool areWeSureThatTheuserHaveDocument = false;
// we listen to auth changes of new user
FirebaseAuth.instance.authStateChanges().listen((user) {
// we want this method to get triggered only when the user authenticates, we don't want it to get executed when the user signs out
if(user != null && !areWeSureThatTheuserHaveDocument) {
// here we create the document
await DatabaseService(uid: user.uid).createUserData(
name: name,
email: email,
);
// now if the document does exists, it will return true, for future checks on this method it will not be executed
areWeSureThatTheuserHaveDocument = await doesUserDocumentExists(user.uid);
}
});
// this is the check document existence
Future<bool> doesUserDocumentExists(String id) async {
final collection = await FirebaseFirestore.instance.collection("users").get();
return collection.docs.map((doc) => doc.id).contains(id);
}
Actually, if you're willing to implement this code or something similar to it, you might want to know that by this you can make sure 100% of the user has a document in the database, but it will cost you one additional read to check that existence od document.
Since you tagged with google-cloud-functions, doing the create-user-and-write-profile-document would reduce the chances of having the type of interruption that you talk about.
But my approach is typically to either write the profile document each time the onAuthState changed listener for a user gets a value, or to check for the existence of a document at that time and create it if needed.

Why User Logs In But is Not Authenticating Sporadically In Firebase?

So I have a weird bug that I can't seem to track down. I'm using firebase functions on the backend and SwiftUI. My login flow goes like this:
User logs in from loginView. The loginView then uses a callback to pass a user to move on to the next View after a user logs in.
After this a user is passed to the View where it calls the firebase functions.
The problem is that every once in a while a user fails authentication. This doesn't happen every time and usually happens when a user has not logged in for 12 hours or more. I thought it may have been a race condition at first but after further investigation decided that it wasn't given the fact that it's using a callback.
Has anyone else experienced anything similar? If so is there any way to fix this?
I have tried making my firebase function stay warm by setting minimum instances to 1 because I initially thought it may be a cold start issue but this hasn't helped.
Any help would be greatly appreciated. Thanks.
On the frontend the code is pulling like so:
FirebaseAuthService().signIn(email: email, password: password) { result, error in
if (error?.occurred) != nil {
self.errorMessage = error!.details
self.state = .failed
self.showErrorAlert = true
return
}
if (localAuthEnabled) {
...... This piece of code works
FirebaseFirestoreService().fetchUser(userID: result?.user.uid ?? "", completion: { user, error
....... This piece of code works.
}
})
}
User is then taken to another view AFTER logging in
This view pulls from 5 or so firebase functions asynchronously (but the user is already logged in by this point). The function that it fails at is as follows
self.function.httpsCallable("api-report").call() { (result, error) in
... It is at times it gives me an auth error inside of this function.
}
I am using this to log out whenever a user put the app in the background or hits the log out button:
func signOut() -> Bool {
do {
try Auth.auth().signOut()
self.session = nil
return true
} catch let err as NSError {
print("Error signing out: %#", err)
return false
}
}
on the backend the report call does the following with the report. This is a large function. I have only added the call to show whats going on.
exports.handler = async (data, context) => {
if (!context.auth) {
console.log("report error context");
console.log(context);
throw new functions.https.HttpsError('failed-precondition', 'The function must be called ' +
'by authenticated users.', 'User must be authenticated');
}
}
It seems the call to your backend may be happening just as/after the user is logged out, or their token is being refreshed. If it's the token being refreshed, the client should just retry the call in cases such as this.
You could also check whether the token is about to expire, and force a refresh when that is the case, or delay calling the Cloud Function until the token has refreshed.

How to pass parameters to action in Flutter

I have this action
Future<void> signUpAction(Store<AppState> store) async {
try {
// ...
} catch (e) {
// ..
}
}
And I dispatch it like this
store.dispatch(signUpAction);
Now, if I want to pass two paramters, how would I do that? Since there is already one parameter there.
I tried this
Future<void> signUpAction(Store<AppState> store, email, password) async {
try {
// ...
} catch (e) {
// ..
}
}
but then on dispatching, if I do
store.dispatch(signUpAction("some#email.com", "somEPa55word!"));
it says the singUpAction expects 3 parameters, so I don't know very well how to pass only these two
Thank you
The dispatch method expects a specific signature. If your method does not have exactly that signature, you can make an anonymous function on the fly that matches the signature.
In this case, since your method takes not only the store, but also the email and password:
store.dispatch((x) => signUpAction(x, "some#email.com", "somEPa55word!"));

Meteor - no more callbacks for "findOne" function

i'm working on a Meteor project, and I must say that isn't easy at all, especially for one thing: callbacks !
Everything is async, so I wonder how do I must do to get results from my mongodb.
var user = Meteor.users.findOne({username: "john"});
return (user); // sometimes returns "undefined"
...
var user = Meteor.users.findOne({username: "john"});
if (user) // so ok, I check if it exists!
return (user); // Cool, I got my user!
return (); // Ok and what should I return here? I want my user!
I don't want to be dirty and put like setTimeout everywhere.
Anybody has a solution for this ?
EDIT :
I noticed in router.js with console.log that my data is returned 4 times. 2 times with an undefined value and 2 other times with the expected value. In the view, it's still undefined.
Why the router passes like 4 times in this route ? Does it display the first result of the return value in the router ?
What should I return if the find() doesn't find anything ?
EDIT 2: Here is some code to understand.
this.route('profilePage', {
path: 'profil/:_id?',
waitOn: function() {
return [
Meteor.subscribe('article', { prop: this.params._id}), // id can be id or username
Meteor.subscribe('article', { userId: this.params._id}), // id can be id or username
Meteor.subscribe('params'),
Meteor.subscribe('profil', (this.params._id ? this.params._id : Meteor.userId()))
];
},
data: function() {
if (this.params._id) {
var user = Meteor.users.findOne(this.params._id);
if (!user)
user = Meteor.users.findOne({username: this.params._id});
console.log(user);
return user;
}
else if (Meteor.userId())
return Meteor.user();
else
Router.go("userCreate");
}
});
I get this on the console:
http://puu.sh/debdJ/69419911f7.png
(text version following)
undefined
undefined
Object_id: "o3mgLcechYTtHPELh"addresses: (....)
Object_id: "o3mgLcechYTtHPELh"addresses: (....)
findOne(yourId) is a sync method which is equivalent to find({ _id: yourId}, callback). The difference is that find() allows you to define a callback. If you don't pass a callback to find() this method will be sync.
check wrapAsync: http://docs.meteor.com/#/full/meteor_wrapasync
It allows you to code in a sync style with a async operations.
Free lesson on EventedMind: https://www.eventedmind.com/feed/meteor-meteor-wrapasync
My experience thus far is that the Meteor Mongodb package is that the functions do not generally provide callbacks (for some reason insert does...), the functions are atomic (thus sync).
There are meteor packages that can make Mongodb async if you want (I havn't tried any).
I guess this sync approach is in line with the simple maintenance goal of Mongodb. Thinking about it, one of my pet peeves using Node is working with async callback waterfalls/nests, they are a pain to create and maintain... and hopefully this will make my code easier to read and understand and change...
var future = new Future();
var _h = Hunts.findOne({huntId});
if(_h) {
future.return(_h)
} else {
return future.wait();
}
on server/startup.js you need:
Future = Npm.require('fibers/future');