How to pop a modal stack and navigate from the same function - xamarin.forms

I'm working a personal app for myself and I'm making use of the Prism library and the NavigationService that it implements.
I have a function called NavigateToFlowAsync() that is defined as follows:
public async Task NavigateToFlowAsync(){
await NavigationService.GoBackAsync(true);
await NavigationService.NavigateAsync($"{nameof(NavigationPage)}/{nameof(NewPage)}");
}
This function will be called from a page that is part of the modal stack that is being popped with the GoBackAsync(true) call made in NavigateToFlow().
The behavior I'm getting at the moment is that the current page is popped, but the subsequent navigation does not occur.
I've tried debugging and looking at the resulting INavigationResult that is returned from both of these navigation calls, but in both cases the result is successful.
For example, if my navigation stack looks like this:
NavigationPage (which has been navigated to modally) -> PageOne -> PageTwo -> PageThree
And PageThree contains the NavigateToFlowAsync() function, I would expect the resulting NavigationStack to look like:
NavigationPage (which has been navigated to modally) -> NewPage
However what it actually looks like is:
NavigationPage (which has been navigated to modally) -> PageOne -> PageTwo
The best theory I have at the moment is the NavigateAsync call doesn't work because at that point the ViewModel that is making the call corresponds to a page that is no longer in the stack.
Any help would be really appreciated, thank you!

Try queueing the actions, so that they are independent of the original page:
Device.BeginInvokeOnMainThread(async () =>
{
await NavigationService.GoBackAsync(true);
await NavigationService.NavigateAsync($"{nameof(NavigationPage)}/{nameof(NewPage)}");
});
Note: I have deliberately NOT done await Device.BeginInvoke.... The goal is to take those two lines, and queue a task to MainThread that will run them after the containing method returns.

I decided that it would make sense to ask the very people that created Prism about this issue, cause if anyone's gonna have an answer it will be them!
Brian Lagunas has said "You can't navigate from a page you just removed from the navigation stack."
So there we go, unfortunately what I was after just can't happen. I'll find another way to get around it!

Related

Fluxor: An effect which doesn't dispatch an action?

I'm using Fluxor to manage state in a Blazor wasm app.
I have the following effect which is triggered after getting a result from deleting an item:
[EffectMethod]
public Task HandleDeleteBudgetResultAction(DeleteBudgetResultAction action, IDispatcher dispatcher)
{
if (action.Success)
{
NavigationManager.NavigateTo("/budgets", false);
}
return Task.CompletedTask;
}
Essentially, if the delete was successful, navigate back to the list page. If it wasn't, do nothing as we need to remain on the detail page.
In this scenario I do not need to dispatch an action, but I have to include the dispatcher parameter, as demanded by the EffectMethod attribute. And since I have no async processes in this method, I am returning Task.CompletedTask.
This obviously feels wrong, so my question is: is this a limitation of Fluxor, or have I architected the flow incorrectly? As far as I'm aware, an effect doesn't have to dispatch an action.
I was thinking I might need to move my navigation state into the store, but I think I'll just come across the same problem again because I'll still need to call NavigationManager from somewhere.
Any help or better solutions appreciated :)
It is a limitation of Fluxor. You can dispatch a GoAction instead of injecting the NavigationManager, as long as you have called UseRouting on the Fluxor options.
builder.Services.AddFluxor(options => options
.UseRouting()
.ScanAssemblies(typeof(Program).Assembly)

Troubleshooting: Redux & Redux Dev Tools -- Action "logjam" -- Actions are not appearing... then appearing all at once on next action

Problem
Actions in my redux store are appearing to log-jam behind one another. I'm iterating through a set of thunks, which each call a number of actions to show they've started, succeeded, etc. When this happens, an action appears for a second in redux dev tools, then is erased.
If I post another action, then all the actions appear all at once, like container ships following the ever-given.
Link to gif of the issue
In this gif I connect to a testing database, afterwards, a number of operations dispatch. I can see those operations in the console, but not devTools. Then, I post another action via the onscreen button, and all the actions flow through at once.
I'm hunting for instances of mutated state, but all reducers destructure state into a new object via:
let newState = {...state}
Any tips?
EDIT:
When I dispatch the same operation from behind a button element, it works just fine. The code that's log jamming is being called by an event listener attached to an event emitter... maybe this has something to do with it?
After debugging, I've traced the problem back to the redux replaceReducer method. I call it 3 times in this sequence. The first and second invocation works fine, but on the third - the store stops receiving actions.
store.injectReducer = (key, asyncReducer) => {
storeTools.dispatchAction({type:"STORE_INJECT_REDUCER_" + key})
store.asyncReducers[key] = asyncReducer;
let combinedReducers = createReducer(store.asyncReducers);
storeTools.dispatchAction({type:"STORE_INJECT_REDUCER_" + key})
store.replaceReducer(combinedReducers);
storeTools.dispatchAction({type:"RESET"})
console.log("replaceReducer")
}
^^^
This code prints actions on the first 2 invocations, but on the third, it prints the first two actions, but not the third.
This bug was caused by invoking "replaceReducer" multiple times within the same thread. From what I now understand - if you call replaceReducer in a forEach loop... you're gunna have a bad time.
My solution was to create a function that stages multiple reducers - then calls replaceReducer once.
May folks from the future benefit from this knowledge.

How can I use collection.find as a result of a meteor method?

I'm trying to follow the "Use the return value of a Meteor method in a template helper" pattern outlined here, except with collections.
Essentially, I've got something like this going:
(server side)
Meteor.methods({
queryTest: function(selector) {
console.log("In server meteor method...");
return MyCollection.find(selector);
}
});
(client side)
Meteor.call('queryTest', {}, function(error, results) {
console.log("in queryTest client callback...");
queryResult = [];
results.forEach(function(result) {
// massage it into something more useful for display
// and append it to queryResult...
});
Session.set("query-result", queryResult);
});
Template.query_test_template.helpers({
query_test_result: function() {
return Session.get("query-result");
}
});
The problem is, my callback (from Meteor.call) doesn't even get invoked.
If I replace the Method with just 'return "foo"' then the callback does get called. Also, if I add a ".fetch()" to the find, it also displays fine (but is no longer reactive, which breaks everything else).
What gives? Why is the callback not being invoked? I feel like I'm really close and just need the right incantation...
If it at all matters: I was doing all the queries on the client side just fine, but want to experiment with the likes of _ensureIndex and do full text searches, which from what I can tell, are basically only available through server-side method calls (and not in mini-mongo on the client).
EDIT
Ok, so I migrated things publish/subscribe, and overall they're working, but when I try to make it so a session value is the selector, it's not working right. Might be a matter of where I put the "subscribe".
So, I have a publish that takes a parameter "selector" (the intent is to pass in mongo selectors).
On the client, I have subscribe like:
Meteor.subscribe('my-collection-query', Session.get("my-collection-query-filter"));
But it has spotty behaviour. On one article, it recommended putting these on Templates.body.onCreate. That works, but doesn't result in something reactive (i.e. when I change that session value on the console, it doesn't change the displayed value).
So, if I follow the advice on another article, it puts the subscribe right in the relevant helper function of the template that calls on that collection. That works great, but if I have MULTIPLE templates calling into that collection, I have to add the subscribe to every single one of them for it to work.
Neither of these seems like the right thing. I think of "subscribing" as "laying down the pipes and just leaving them there to work", but that may be wrong.
I'll keep reading into the docs. Maybe somewhere, the scope of a subscription is properly explained.
You need to publish your data and subscribe to it in your client.
If you did not remove "autopublish" yet, all what you have will automatically be published. So when you query a collection on client (in a helper method for example), you would get results. This package is useful just for quick development and prototyping, but in a real application it should be removed. You should publish your data according to your app's needs and use cases. (Not all users have to see all data in all use cases)

Can someone explain how Meteor.defer() works?

So... Meteor.defer(function(){ // stuff }) isn't in the docs:
https://github.com/meteor/meteor/issues/2176
But this links seems to say that it's simply equivalent to
Meteor.setTimeout(function(){ // stuff }, 0);
If that's the case, how does this do, um, anything? It's basically saying "wait for 0 ms and then run the function".
So... it runs the function instantly.
What am I missing here? Is this kind of like Tracker.afterFlush or something? Does it somehow wait for "things" (what kind of things?) to finish before running?
I see Meteor.defer() a lot on SO being used as a bit of a hack on added helper methods to run after the dom is (somewhat) loaded - basically to get the same effect as running code inside of a Template.foo.rendered method.
However, the main (and best) use of Meteor.defer is to run a task asynchronously.
Let's say we have an app where we are sending an email. On the server, it may take several seconds for that to process inside of a meteor method, slowing down your application drastically. However, if you wrap that process in a Meteor.defer the email processing won't block execution, the email still sends (when it gets a chance, not instantly), but everything runs much faster, since the code that follows isn't waiting. There is a great example lesson about deferring execution at Bulletproof Meteor.
You can actually get the same effect with a setTimeout(f,0) - if you have a slow function, you could wrap it in the setTimeout and the rest of the code will complete, and 'defer' the slow process in the timeout, so although it doesn't seem like it, setTimeout(f,0) does actually have a pretty useful purpose!
To see an example of this in action, here's a fiddle, open the console and watch where 'foo' logs.
I faced some issue in my project because of asynchronous callback. Inside onCreated i was making a server Meteor.call and set the response inside reactiveVar. And i was doing something inside onRendered with that reactiveVar. Every time reactiveVar was showing undefined.
So i used Meteor.defer(function(){...}) inside onRendered and it sloved my issue.
Here is some demo with and without using Meteor.defer()
Template.myTemplate.onCreated(function () {
var instance = this;
instance.myTemplateModel = new ReactiveDict();
Meteor.call('all-user', function(err, res){
if(res){
console.log('inside callback');
instance.myTemplateModel.set('users', res);
}
});
});
Template.myTemplate.onRendered(function () {
var instance = this
console.log('rendered start');
Meteor.defer(function(){
console.log(instance.myTemplateModel.get('users'));
});
console.log('render end');
});
Console:
/*Without Meteor.defer()*/ | /*With Meteor.defer()*/
render start | inside callback
undefined | render start
render end | render end
inside callback | [Object, Object, Object]

In Meteor, is there a way to detect when data is fully loaded to the browser?

This problem probably has been asked before, but I cannot seem to figure out an easy way to do it.
I have a Meteor page that shows messages posted by users in chronological order (newest message at the bottom). I want the page to:
#1) Subscribe to the messages (using publish/subscribe) based on a parameter supplied in the URL
#2) Render the page
#3) Display the messages on the page
#4) Scroll to the bottom.
There's no apparent way to know when 1, 2, and 3 are complete before initiating the scroll to bottom. Meteor does have an observe/added function to do an event when new messages are added to a subscription, however that's only when documents are insertted into Mongo, and it does not trigger when displaying results to the initial subscription.
I'll show code for the steps above:
#1: Subscribe to the messages using publish/subscribe: in /client/messages.js
Template.messages.created = function() {
this.autorun( function() {
this.subscription = Meteor.subscribe("messages", Router.current().params.category);
}.bind(this));
};
#2 Render the page, in /client/messages.html
<template name="messages">
{{#each messages}}
{{messageText}}<br><br>
{{/each}}
</template>
#3: Display the mssages on the page: /client/messages.js
Template.messages.helpers({
messages: function() {
var category = Router.current().params.category;
return Messages.find({category: category}, { sort: { messageDate: 1 } });
},
});
All this works, but does not automatically scroll to the bottom.
I cannot add a jquery scroll command to the Meteor onRendered section because it runs BEFORE the data is written to the DOM. I can do a Meteor.setTimeout to wait 1 second before scrolling to the bottom, but does not work if it takes longer than a second to fill the DOM with subscribed data.
Here's another thing complicating the matter. I am supplying the category variable in the URL. When the client selects another category, using Meteor/Blaze pathFor,
{{pathFor 'messages' channelid='new'}}
the browser does not reload/rerender the page, it simply updates the URL parameter which triggers the autorun to change what messages it has subscribed to. It simply writes the new data to the DOM. Because of this, I cannot to a jquery $(document).ready to detect whether the page is ready (because the page is always ready), and I cannot use some fancy handlebars thing like {{scrollToBottom}} at the end of my {{#each}} in messages.html because that it not re-run.
So, is there a simple way to detect when Meteor/Blaze completely finishes writing new data to the browser?
If I understand correctly, you really just want to know when all of your data is published from the server to the client (so you can then do something, in this case, scroll to the bottom).
All calls to Meteor.subscribe() return a subscription handle. This has a ready() method, that tells you all the data has been sent. This includes those done at a template level (which you might want to consider if appropriate for your use-case). More information here:
http://docs.meteor.com/#/full/Blaze-TemplateInstance-subscribe
There are several ways to use this - you need to decide what is appropriate for you. As ready() is reactive, you can just use this. However, you might find it easier to implement an onReady callback within your subscription (see documentation above).
I suspect you will need to implement an autorun within your template rendered. This is to avoid the reverse scenario where the data arrives before the rendering (and so the callback fires too early). Something like this should do the trick (noting you have already set your subscription handle in your template creation function to this.subscription):
Template.messages.onRendered(function() {
var self = this;
this.autorun(function(computation) {
if(self.subscription.ready()){
$(document).scrollTop($(document).height());
computation.stop()
}
});
});
This should ensure both that the function is only called after rendering and the data from the subscription is complete. The autorun is also stopped after executing the scroll to stop continued calling should new documents cause ready() to no longer be truthy (and then become ready again) - which might surprise people with the page re-scrolling to the bottom!

Resources