I am using Ionic 4 and the Loading Controller.
async presentLoading() {
const loading = await this.loadingCtrl.create({
message: 'wait. . .'
});
return await loading.present();
}
Works great. But now I want to dismiss the loader programatically.
this.loadingCtrl.dismiss().then(a => console.log('dismissed'));
Even though I see 'dismissed' on my console (logging worked) the loading overlay continues. Any idea what might be the reason?
You not dismissing the actual loader that is displayed. You have to dismiss it with loading variable like below :
loading.dismiss().then(a => console.log('dismissed'));
Notice that I have used variable loading that you have declared for current loader.
If you want to dismiss programmatically use this in your service.
export class LoaderService {
private isLoading = false;
constructor(private loadingController: LoadingController) {
}
async presentLoading() {
// issue generated! so we used Boolean value to set loader dismissed call
firstly so we used this logic
this.isLoading = true;
let loading = await this.loadingController.create({
message: 'Please Wait',
spinner: 'bubbles'
}).then((res) => {
res.present();
if (!this.isLoading) {
// res.dismiss().then(()=> console.log('abort presenting'));
this.loadingController.dismiss().then(() =>
console.log('Dismissed'));
}
});
return loading;
}
async hideLoading() {
this.isLoading = false;
return await this.loadingController.dismiss().then(() => console.log('Dismissed'));
}
}
Related
here is a popup screen to add the transaction to the app, as you can see here
and when the add button pressed the data will add to database and also to the dislpay , here is the code
ElevatedButton(
//on pressed
onPressed: () async {
final _categoryName = _nameEditingController.text;
if (_categoryName.isEmpty) {
return;
}
final _type = selectedCategoryNotifier.value;
//sending the data to model class
final _category = CategoryModel(
id: DateTime.fromMillisecondsSinceEpoch.toString(),
name: _categoryName,
type: _type,
);
//inserting the data to database
await CategoryDb.instance.insertCategory(_category);
//refreshing the ui
await CategoryDb.instance.refreshUI();
//and quitting the popup screen
Navigator.of(ctx).pop();
},
child: const Text('Add'),
),
and in this code you can see that I called 2 functions that for insert data and also refresh the UI, in the refresh UI function I added the function that to get all data from database to screen, here the code of all functions for CRUD operatins
const databaseName = 'category-database';
abstract class CategoryDbFunctions {
Future<List<CategoryModel>> getCategories();
Future<void> insertCategory(CategoryModel value);
}
//CRUD operations code
class CategoryDb implements CategoryDbFunctions {
CategoryDb._internal();
static CategoryDb instance = CategoryDb._internal();
factory CategoryDb() {
return instance;
}
ValueNotifier<List<CategoryModel>> incomeCategoryListListener =
ValueNotifier([]);
ValueNotifier<List<CategoryModel>> expenseCategoryListListener =
ValueNotifier([]);
#override
Future<void> insertCategory(CategoryModel value) async {
final _categoryDB = await Hive.openBox<CategoryModel>(databaseName);
await _categoryDB.add(value);
await refreshUI();
}
#override
Future<List<CategoryModel>> getCategories() async {
final _categoryDB = await Hive.openBox<CategoryModel>(databaseName);
return _categoryDB.values.toList();
}
Future<void> refreshUI() async {
final _allCategories = await getCategories();
incomeCategoryListListener.value.clear();
expenseCategoryListListener.value.clear();
await Future.forEach(
_allCategories,
(CategoryModel category) {
if (category.type == CategoryType.income) {
incomeCategoryListListener.value.add(category);
} else {
expenseCategoryListListener.value.add(category);
}
},
);
}
}
so I checked the all things , but I couldn't find where I'm missing parts,
and here is the main part, it is adding to the database also displaying after I refresh the UI or change the tab here you can see what I mean by 'changing the tab'
this is the problem I'm trying to fix this for 2 day, i couldn't find any solution or mistake in my code
There many ways you can handle this problem.
but I dont see where you notify youre ui that the data has been changed, flutter does only update the ui when you use setState etc.. these functions help flutter updating the ui where the data changed.
i would recommend you to use setState in the place you invoke youre dialog.
onTap:(){
setState(){
await dialogStuff();
}
}
[Edited] I have this application with multilevel user application where I have functions based on roles. Currently, I am saving user response in shared preferences and fetching it by getting it's instance whenever I need it. And also, I am using different screens and different widgets for each role. But there has to be a better way to do it. I am so confused with singleton pattern and making global variables in dart.
Here's my code:
void main() {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences.getInstance().then((prefs) {
var user=prefs.getString("role");
runApp(MultiProvider(
providers: [
ChangeNotifierProvider<RoleNotifier>(
create: (_) => RoleNotifier(user),
),
],
child: MyApp(),
));
});
}
void setRole(String role) async {
Provider.of<RoleNotifier>(context, listen:false).setUser(role);
await SharedPreferences.getInstance().then((prefs){
prefs.setString("role", role);
});
}
_login() async {
try {
setState(() {
_isbusy = true;
});
var data = {"username": _emailc.text, "password": _pass.text};
var response = await CallApi().postData(data, 'login');
SharedPreferences local = await SharedPreferences.getInstance();
var res = response.data;
print(res);
if (res['success']) {
local.setString('token', res['data']['token']);
if (res['data']['role'] == 'admin') {
setRole(res['data']['role']);
local.setString('info', json.encode(res['data']));
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => AdminDashBoard()));
} else if (res['data']['role'] == 'dev') {
setRole(res['data']['role']);
local.setString('post', res['data']['role']);
local.setString('info', json.encode(res['data']));
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => DevDashBoard()));
} else if (res['data']['role'] == 'user') {
setRole(res['data']['role']);
local.setString('post', res['data']['role']);
local.setString('info', json.encode(res['data']));
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => UserDashBoard()));
}
} else {
print('error');
setState(() {
_isbusy = false;
});
showSimpleFlushbar(context, "An Error Occurred!");
}
} on DioError catch (e) {
print(e);
setState(() {
_isbusy = false;
});
print(e.response.data);
print(e.response.headers);
print(e.response.request);
showSimpleFlushbar(context,
"Login Failed! Please Check your credentials and try again.");
}
}
And to access the variables:
SharedPreferences.getInstance().then((prefs) {
var data = jsonDecode(prefs.getString("info"));
setState(() {
email = data['email'];
post = data['role'];
});
});
The problem is, I have to run this on initState in every screen and there is a delay in fetching data which throws an exception for small time.
I just figured out this is working.
(Provider.of<RoleNotifier>(context).getUser()=="admin")?AdminWidget():SizedBox(),
Now I can access the data from anywhere using provider. But is there any better way to do this? I've heard a lot about singleton pattern and in my case even though it works, it seems like I am doing something wrong. Like I am listening to the value that is static immediately after login is completed.
SharedPreferences prefs;// file level global variable
main(){
SharedPreferences.getInstance().then((p)=>prefs = p);
// do whatever
runApp(MyApp());
}
Now, don't use SharedPreferences.getInstance() when needed but use the global variable
created.
Like
prefs.getString('name');
or
prefs.setString('foo','bar');
For example
class Foo extends StatelessWidget{
Widget build(context){
var name = prefs.getString('name');// don't use var prefs = await SharedPreferences.getInstance();
return Text("name is $name");
}
}
Why not create a User class and extend it with Provider?
Then based on the Consumers to build dynamic widgets you can pump out what ever you want based on the User.role for the selected user.
In your Singleton you can add a Singleton().selectedUser var and once a user logs in or what ever process they follow you can assign it to that. Use this selectedUser var for your Provider.value.
If you need example code let me know.
I created a simple function of creating a loading like this
async presentLoading() {
const loading = await this.loadingController.create({
message: 'Please Wait...',
});
await loading.present();
}
And i am closing the loader when the data is fetch like this
getUserData(){
console.log(this.userID);
this.api.getCompanyEmploye(this.userID).subscribe(res => {
this.loadingController.dismiss(); //closing here
console.log(res);
this.user = res.records[0];
this.familyMembers = res.records[0].family_members;
});
}
I am calling both function in constructor
constructor(public loadingController: LoadingController){
this.presentLoading();
this.getUserData();
}
Its showing error of ERROR Error: Uncaught (in promise): overlay does not exist
The issue is that your API call responds sooner than the loading controller gets instantiated. Instead of parallel calls, you should try to serialize those this way:
Make your presentLoading method to return Promise:
async presentLoading() {
const loading = await this.loadingController.create({
message: 'Please Wait...',
});
return loading.present();
}
Now you can call it this way:
getUserData(){
this.presentLoading().then(()=>{
this.api.getCompanyEmploye(this.userID).subscribe(res => {
this.loadingController.dismiss(); //closing here
console.log(res);
this.user = res.records[0];
this.familyMembers = res.records[0].family_members;
});
})
}
And in your constructor you need only to call for the API
for me, the issue is simply because I don't have .catch() for the promise. As#Sergey suggested, this is because the loader is not ready when you calling the ionic loader
this.loadingController.dismiss()
.then(async () => {
await this.setStorageForLocalData(data);
})
.catch(console.error);
where .catch() will dismiss the error
Is anyone know how to give css of loading controller in ionic 4? I have tried all the solution which is availbale online but none of its working,so please help me.
I have made one service called loadingService for loading controller then i give css in gloabal.scss or variable.scss is still not working.here is my code.
loadingService.ts
async present() {
this.isLoading = true;
return await this.loadingController.create({
spinner: "lines-small",
duration: 10000,
cssClass:'loader',
}).then(a => {
a.present().then(() => {
console.log('presented');
if (!this.isLoading) {
a.dismiss().then(() => console.log('abort presenting'));
}
});
});
}
async dismiss() {
this.isLoading = false;
return await this.loadingController.dismiss().then(() => console.log('dismissed'));
}
gloabal.scss
.loader {
--background: #600001;
--spinner-color: yellow;
}
Please help me on it, and yes i am using ionic 4 so please give me ionic 4 solution not ionic 3.
I am using ionic4 LoadingController in the app. it is dismissed after a given interval of time, but don't want like that. I don't want to set the time. before calling web api i will display LoadingController and I want to dismiss it whenever I will get the response from rest service. can anyone help me how to implement it?
You should do something like this:
async function presentLoading() {
const loader= document.querySelector('ion-loading-controller');
await loader.componentOnReady();
const element = await loader.create({
message: 'Please wait...',
spinner: 'crescent',
duration: 2000
});
return await element .present();
}
// Initialize variable
private loader: any;
private loaderActive: boolean = false;
// Generic method to display the loader
async showLoader() {
this.loaderActive = true;
this.loader = await this.loadingCtrl.create({
message: 'Please wait...',
spinner: 'crescent',
});
await this.loader.present();
}
// Generic method to dismiss the loader
async dismissLoader() {
if (this.loaderActive === true) {
await this.loader.dismiss();
}
this.loaderActive = false;
}
Before api call just call the method this.showLoader(), once you get the response
just call the this.dismissLoader() method.
I would take a guess and say that you are using HttpClient to get your data from your rest service, therefore I would propose this scenario for you,
First of all we start by displaying the loader:
let loader = await this.loadingCtrl.create({
//Your loader content and options
});
loader.present();//show the loader
After that we get the data using the HttpClient. let suppose that we injected that's way public http: HttpClient so it depends on your rest service http mthod (post, get, put ...). the code would be:
this.http.get("url").subscribe((data: any) => {
//process your data here
loader.dismiss();//then we hide the loader
});
Leave me a comment if I misunderstood it.