Ionic 4 loading component changes from v3 - ionic-framework

I'm trying to refactor this function for ionic 4. Trouble is half of these imports don't exist any longer. Has anyone got any idea where OverlayBaseController, Loading are now in v4?
import {ToastController, LoadingController, OverlayBaseController, Loading } from "ionic-angular"
/**
* Wraps an asynchronous call with the LoadingController overlay
* #param action the action to execute
* #param loadingMessage the loading message to display
* #returns {Promise<T>}
*/
async loading<T>(action: Promise<T>, loadingMessage?: string): Promise<T> {
// Delay showing the loading spinner for 0.4s
// See http://yuiblog.com/blog/2010/11/11/improving-perceived-site-performance-with-spinners/
// Hopefully delay is re-implemented. See https://github.com/ionic-team/ionic/pull/11583
let loadingOptions: OverlayBaseController = {} // delay: 400
if (loadingMessage)
loadingOptions.content = loadingMessage
let loader: Loading = await this.loadingCtrl.create(loadingOptions)
await loader.present().catch(this.log.warn)
try {
let result: T = await action
loader.dismiss()
return result
} catch (e) {
loader.dismiss()
throw e
}
}

What about the following? I've used the docs as a reference, there seems to no need to use OverlayBaseController or Loading.
import { LoadingController } from '#ionic/angular';
async loading<T>(action: Promise<T>, loadingMessage?: string): Promise<T> {
const loader = await this.loadingCtrl.create({
message: loadingMessage
});
await loader.present().catch(this.log.warn);
try {
const result: T = await action;
loader.dismiss();
return result;
} catch (e) {
loader.dismiss();
throw e;
}
}
I removed the comment about the delay. The comment seems obsolete and confusing as there is no option to delay a loading screen...
I also went from let to const whenever possible, but this shouldn't harm the functionality.
Also, it might be a good idea to handle the case if loadingMessage is undefined. Maybe you could show a generic message like Please wait... This could be done via a default parameter. Therefore you'd have to change the signature like so:
async loading<T>(action: Promise<T>, loadingMessage = 'Please wait ...'): Promise<T>
For more info on default parameters, you can also check out the Typescript docs.

Related

Blazor WASM Load Data before Render Page

I would like to load some Data before I Render my Blazor Application because in depndency to the loaded data I would like to render my app (layout, navbar ...)
Now I want to use the OnInitialised method instead of OnInitialisedAsync and with no async and await keywords.
But now I had a problem to convert the data which I get back from my API.
protected override void OnInitialized()
{
try
{ Console.WriteLine("Test1Mainasync");
LoadCategories();
}
catch (Exception e)
{
jsRuntime.ToastrError(e.Message);
}
}
private void LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = categorieService.GetAllCategories();
SD.Categories = CategoriesInit1.ToList();
//foreach(var categorie in CategoriesInit){
// SD.Categories.Append(categorie);
//}
Console.WriteLine("Test1Main");
}
Has someone an idea why this converting issues happen?
I think you have this method:
public async Task<IEnumerable<CategorieDTO>> GetAllCategories()
and you should call it this way:
private async Task LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = await categorieService.GetAllCategories();
and:
protected override async Task OnInitializedAsync()
{
try
{ Console.WriteLine("Test1Mainasync");
await LoadCategories();
}
Has someone an idea why this converting issues happen?
In your code CatagiesInit1 is a Task, it's not a List<CategorieDTO>. You only get the List<CategorieDTO> when the task completes which you have no control over as you don't await the completion of the Task. In all likelyhood, your sync code will run to completion before that happens.
If your CategoryService returns a Task then the code that handles it must be async code. You can't escape from the async world back into the sync world without consequencies. If you want to live in the sync world then all the data pipeline code also needs to be blocking sync code.
If I understand your comments correctly, you want nothing to render until a certain set of conditions are met. If so add some standard Loading... component code to the page if it's page specific or App.razor if it's on initial load, or say MainLayout if it's application wide.
Here's a quick an dirty example:
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
#if (Loaded)
{
<RouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
<FocusOnNavigate RouteData="#routeData" Selector="h1" />
}
else
{
<div class="m-2 p-5 bg-secondary text-white">
<h3>Loading.....</h3>
</div>
}
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
#code {
private bool Loaded;
protected override async Task OnInitializedAsync()
{
Loaded = false;
// simulate getting the data first
await Task.Delay(5000);
Loaded = true;
}
}
Your call to API endpoint return an awaitable task but not the IEnumerable, So you can not assign awaitable task to IEnumerable so this piece of code wont work
private void LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = categorieService.GetAllCategories();
}
You should have your LoadCategories function like this
private async Task LoadCategories()
{
IEnumerable<CategorieDTO> CategoriesInit1 = new List<CategorieDTO>();
CategoriesInit1 = await categorieService.GetAllCategories();
}
API calls should be awaitable, else it will stuck your UI
You can use this solution as well
private void LoadCategories()
{
var t = Task.Run(() => categorieService.GetAllCategories()()).GetAwaiter();
t.OnCompleted(() =>
{
CategoriesInit1 = t.GetResult();
// you may need to call statehaschanged as well
StateHasChanged();
});
}

Native Dialog for location setting on Flutter

Is there a way to implement a Dialog for Location setting like the image below which gets triggered when app requires GPS location and doesn't find. Hitting OK will right away turn on the system GPS. This seems more convenient for users instead of taking them to location and manually turn on.
Is it possible to implement such thing in Flutter?
Expanded View of dialog:
Credits to Rajesh, as answered here. The plugin lets you add this native dialog for a quick location setting.
The implementation is quite simple as this:
import 'package:location/location.dart';
var location = Location();
Future _checkGps() async {
if(!await location.serviceEnabled()){
location.requestService();
}
}
#override
void initState() {
super.initState();
_checkGps();
}
In Kotlin, try this code:
class HomeActivity : AppCompatActivity() {
private val requestLocation = 199
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableLoc()
}
private fun enableLoc() {
val mLocationRequest = LocationRequest.create()
mLocationRequest.interval = 10000
mLocationRequest.fastestInterval = 5000
// mLocationRequest.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
val builder = LocationSettingsRequest.Builder()
.addLocationRequest(mLocationRequest)
val client = LocationServices.getSettingsClient(this)
val task =
client.checkLocationSettings(builder.build())
task.addOnSuccessListener(this) {
// All location settings are satisfied. The client can initialize
// location requests here.
// ...
}
task.addOnFailureListener(this) { e ->
if (e is ResolvableApiException) {
// Location settings are not satisfied, but this can be fixed
// by showing the user a dialog.
try {
// Show the dialog by calling startResolutionForResult(),
// and check the result in onActivityResult().
e.startResolutionForResult(
this,
requestLocation
)
} catch (sendEx: SendIntentException) {
// Ignore the error.
}
}
}
}
}

How to handle two calls and the loading controller in Ionic 4

I have a requirement where I have 2 API calls, and I want the first two calls to be there for the first request. And 2nd API call to be there when navigated back.
I am calling 1st API in ngOnInit webhook and 2nd API on ionViewWillEnter webhook.
The issue which I am facing is sometimes my loader doesn’t get dismissed when both of the request complete at the same time.
So the possible solution which I am thinking is that if I could call both APIs on the first load synchronously and thereafter call another API every time the back button is clicked.
NOTE: I am using loaders in my interceptor.
CODE: For interceptor
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Clone the request to add the new header.
const authReq = req.clone();
this.showLoading();
// send the newly created request
return next.handle(authReq).pipe(catchError((error) => {
if (error.status === 401) {
this._navCtrl.navigateForward('/login');
}
return throwError(error);
}), finalize( () => {
console.log('hi');
this.dismissLoading();
})
);
}
EDIT:
Code to show loader and hide loader:
async showLoading() {
return await this._loadingCtrl.create().then(a => {
a.present();
});
}
async dismissLoading() {
return await this._loadingCtrl.dismiss();
}
In my case, I will create a LoaderService to handle the Loading by myself. The special thing is I will create a flag called isShowing, so if the loading is already showing, we just need to update the loading message by calling presentLoader function again. There will be only one Loading popup show up on your screen.
In your case, I would not recommend to display the Loader in HTTP Interceptor because we cannot handle the HTTP call stack there. Just create a new function that combines all necessary API calls and show/dismiss popup when you start/finish processing the data.
import { LoadingController } from '#ionic/angular';
import { Injectable } from '#angular/core';
#Injectable()
export class LoaderService {
private loading: HTMLIonLoadingElement;
private isShowing = false;
constructor(private loadingController: LoadingController) {}
public async presentLoader(message: string): Promise<void> {
if (!this.isShowing) {
this.loading = await this.loadingController.create({
message: message
});
this.isShowing = true;
return await this.loading.present();
} else {
// If loader is showing, only change text, won't create a new loader.
this.isShowing = true;
this.loading.message = message;
}
}
public async dismissLoader(): Promise<void> {
if (this.loading && this.isShowing) {
this.isShowing = false;
await this.loading.dismiss();
}
}
}
The simple solution would be to make a function call whenever you click the bak button and inside the function you can make a API call.
Instead of linking to the back button you can use ionViewWillEnter, which is called whenever you are about to leave a page but the downside would be it is called every time view is changed regardless of the fact that only when back button is clicked.
Also you should check, is your service singleton and it creates a single instance of ionic-loader. I think in your case more than one instance of loader is being created.
Also instead of calling the loader in interceptor, you can call showLoading() in ionViewWillEnter and hideLoading() in ionViewDidEnter() of your page.
You can create a Singleton Loader Service as shown below.
This service will take care of creating only a single instance of ionic loader.
import { Injectable } from '#angular/core';
import { LoadingController } from '#ionic/angular';
#Injectable({
providedIn: 'root'
})
export class LoaderService {
private loader: HTMLIonLoadingElement;
constructor(private loadingController: LoadingController) {}
async showLoader() {
if (!this.loader) {
this.loader = await this.loadingController.create({ message: 'Loading' });
}
await this.loader.present();
}
async hideLoader() {
if (this.loader) {
await this.loader.dismiss();
this.loader = null;
}
}
}
private loading: HTMLIonLoadingElement;
constructor(public loadingController: LoadingController) { }
public async show(): Promise<void> {
return await this.loadingController.create({
message: 'Please wait...',
spinner: 'crescent'
}).then(a => {
a.present().then(() => {
console.log('presented');
});
});
}
return await this.loadingController.dismiss().then(() =>
console.log('dismissed'));
}`enter code here`

unable to catch 404 error (resource not found) in Ember JS setupController hook while assigning model to the template

I have a route /users/:user_id in emberjs application and I am assigning a model to the template in setupController hook of a App.UserRoute class as:
controller.store.find('user', model.id).then(onSuccess, onError);
var onSuccess = function(user) {
if(user) {
// reload Model to forcefully fetch from server
user.reload().then(function() { /* some processing */ }, function(error_response) { /* some error handling */ });
// assign model for user
controller.set('model', user);
}
};
var onError = function(reason) { /* some error handling */ };
Now this gives me an error 404 from server when I am looking for an user-id which does not exists. I am trying to catch this error and display appropriate message and redirect to the list of all users (/users). But I am unable to catch it and I get an error from ember : Error while loading route: undefined. How can I achieve this?
The real thing is you are passing in undefined to your then method. You should move your declaration of your callback functions to before the promise code.
var onSuccess = function(user) {
if(user) {
// reload Model to forcefully fetch from server
user.reload().then(function() { /* some processing */ }, function(error_response) { /* some error handling */ });
// assign model for user
controller.set('model', user);
}
};
var onError = function(reason) { /* some error handling */ };
controller.store.find('user', model.id).then(onSuccess, onError);
The reason is that you yourself are catching the failure and processing it via the second argument to then. This turns failure into success. You will need to rethrow the error so it is picked up by the error hook you have defined. One way would be
user.reload().then(
function() { /* some processing */ },
function(error_response) { /* some error handling */ }.catch(RSVP.rethrow)
});
It would also most likely work to do
user.reload().then(function() { /* some processing */ },
function(error_response) {
/* some error handling */
throw "reload failed";
}
);
More generally, consider the following code:
rejected_promise.then(
success1 /* won't be called */
function rejected() { return 0; }
)
.then(
success2 /* will be called */
rejected2 /* won't be called */
);
Here, rejected2 won't be called; rather, success2 will be. That is because a rejection handler is considered to fulfill the promise (make it succeed), unless it rethrows.
There are some implementation details that I think could be improved:
1) You do not need to set your model in the controller. Ember assigns it to the value resolved by the promise in the route model hook.
model: function(params) {
var user = this.store.getById('user', params.id);
if (user) {
return user.reload();
} else {
return this.store.find('user', params.id);
}
}
2) You could catch your promise error in the error event handler of your route.
actions: {
error: function(error) {
// print error
this.transitionTo('users');
}
}
You could take a look at the Ember Guide to check how Error and Loading substates are managed by the router.

UI Thread issue with view model in MVVMCross

I am using MVVMCross with my cross-platform Windows Phone and Android app. In the core project's main view model, I am doing some background work using TPL and I want to make sure that in the callback, when I make changes to the properties of the view model which will trigger UI change, that the code is run on UI thread, how do I achieve this?
For code, here is how it likes
private MvxGeoLocation _currentLocation;
private Task<MvxGeoLocation> GetCurrentLocation()
{
return Task.Factory.StartNew(() =>
{
while (_currentLocation == null && !LocationRetrievalFailed)
{
}
return _currentLocation;
});
}
var location = await GetCurrentLocation();
if (LocationRetrievalFailed)
{
if (location == null)
{
ReverseGeocodingRequestFailed = true;
return;
}
// Show toast saying that we are using the last known location
}
Address = await GooglePlaceApiClient.ReverseGeocoding(location);
Did you try IMvxMainThreadDispatcher?
var dispatcher = Mvx.Resolve<IMvxMainThreadDispatcher>();
dispatcher.RequestMainThreadAction(()=> { .... });
See more on the implementation:
https://github.com/MvvmCross/MvvmCross/search?q=IMvxMainThreadDispatcher&type=Code
Usually I don't think you need this though.
Since you start the async processing from main thread, the async operations should return back to main thread.
Can you give an example of the async code you are doing?
Update on 24th August 2020:
As #claudio-redi has mentioned, ExecuteOnMainThreadAsync needs to be used. But Mvx.Resolve is now obsolete. So the latest snippet would be:
var mainThreadAsyncDispatcher = Mvx.IoCProvider.Resolve<IMvxMainThreadAsyncDispatcher>();
await mainThreadAsyncDispatcher.ExecuteOnMainThreadAsync( async ()=> { await SomeAsyncTask() });
Method RequestMainThreadAction is now obsolete. Today you have to do
var dispatcher = Mvx.Resolve<IMvxMainThreadAsyncDispatcher>();
await dispatcher.ExecuteOnMainThreadAsync(()=> { .... });