I am trying to implement a logout page. So when the user clicks on logout button in the navigation following code is called:
Class Logout extends StatelessWidget {
#override
Widget build(BuildContext context) {
final provider = Provider.of<SignInProvider>(context, listen: true);
Future.delayed(Duration(seconds: 5), () async {
provider.isLoggedIn = false;
provider.notifyListeners();
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => LoginGate()));
});
return Center(child: CircularProgressIndicator());
}
}
I get the following error:
The following assertion was thrown building MainScreen(dirty, dependencies: [_InheritedProviderScope<SelectedIndex?>, _InheritedProviderScope<SignInProvider?>], state: _MainScreenState#6a8ce):
setState() or markNeedsBuild() called during build.
I tried adding the delay hoping that would fix the issue but didn't help. Would appreciate some help on how to handle this.
Logout Button is shown using NavigationRail
const NavigationRailDestination(
icon: Icon(Icons.logout),
label: Text('Logout'),
),
And the Logout widget is called using following:
child: Row(
children: [
NavigationRailExample(),
const VerticalDivider(thickness: 1, width: 1),
Expanded(
child: screenSwitch[providerSelectedIndex.selectedIndex],
)
],
),
List<Widget> screenSwitch = [
HomeScreen(),
Screen1(),
Screen2(),
Screen3(),
Screen4(),
Screen5(),
Screen6(),
Logout(),
];
You are calling you async function in build method which is wrong. Try this:
class Logout extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: doLogOut(context),
builder: (context, snapshot) {
return Center(child: CircularProgressIndicator());
},
);
}
Future<void> doLogOut(BuildContext context) async {
final provider = Provider.of<SignInProvider>(context, listen: true);
await Future.delayed(Duration(seconds: 5), () async {
provider.isLoggedIn = false;
provider.notifyListeners();
Navigator.pushReplacement(
context, new MaterialPageRoute(builder: (context) => LoginGate()));
});
}
}
Related
I have a page that shows a loading while making my API call, and once the call is done it shows the received data.
On debugger everything works correctly, but when I create the apk with 'flutter build apk', and download it, the loading remains indefinitely.
I also put a showDialog at the end of my Provider function that makes the API call (I put this showDialog just below notifyListeners().
I can't understand why in debug it works and in release it doesn't.
(This notifyListeners thing not working just does it for every API call I make)
This is the code of the provider function that makes the api call:
Future<void> getUserSites(context) async {
_userSites.clear();
isLoading = true;
notifyListeners();
try {
final response = await NetworkService.call(
url: '/api/structure/Sites',
method: Method.Get,
context: context) as List<dynamic>;
for (var i = 0; i < response.length; i++) {
_userSites.add(Sites.fromJson(response.elementAt(i)));
}
if (defaultSite == null) {
if (SimplePreferences.getDefaultSite() == null) {
defaultSite = _userSites.isNotEmpty ? _userSites.first : null;
if (defaultSite != null) {
SimplePreferences.setDefaultSite(defaultSite!.id);
}
} else {
defaultSite = _userSites.firstWhere(
(element) => element.id == SimplePreferences.getDefaultSite()!);
}
}
} catch (e) {
inspect(e);
if (SimplePreferences.getToken() != null) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('General Error'),
content: Text(e.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Ok',
),
)
],
),
);
}
// throw e;
}
isLoading = false;
notifyListeners();
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('getUserSites done!'),
content: Text(_userSites.toString()),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: const Text(
'Ok',
),
)
],
),
);
}
this is the Home page code:
class HomePageScreen extends StatelessWidget { const HomePageScreen({super.key}); static const String routeName = '/';
#override Widget build(BuildContext context) { log('New Page: Home Page'); final provider = Provider.of<MyManager>(context);
return provider.isLoading ? const Center(
child: CircularProgressIndicator(),
)
: SingleChildScrollView(
physics: const BouncingScrollPhysics(),
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
MainButton(
onTap: () async {
Navigator.of(context)
.pushNamed(ShowPatrolScreen.routeName);
await provider.getPatrol(context);
},
icon: Icons.home,
title: 'ShowPatrol',
),
printSito(provider.defaultSite?.description ?? 'Nessun Sito', context),
PrintRequestZ(
showCompleted: false,
),
],
),
),
);
}
Widget printSito(String name, context) { .... //pass context for Navigator and Theme } } `
this is the main page:
...
final myScreens = [
const HomePageScreen(),
...
];
#override
void initState() {
// TODO: implement initState
super.initState();
print('token: ${SimplePreferences.getToken()}');
if (SimplePreferences.getToken() == null){
Navigator.of(context).pushReplacementNamed('/Auth');
}
var provider = Provider.of<MyManager>(context, listen: false);
provider.setAll(context); //this function calls all my API calls, but for testing, I commented out all other functions and kept only the one written above
}
#override
Widget build(BuildContext context) {
var provider = Provider.of<MyManager>(context);
return Scaffold(
appBar: const MyAppBar(title: 'Ronda',canGoBack: false,),
body: myScreens[currentPage],
bottomNavigationBar: ...
),
}
Thanks in advance!
after some research i found the solution.
You have to use WidgetsBinding.instance.addPostFrameCallback
in the parent component.
So my home page now looks like this:
#override
void initState() {
// TODO: implement initState
super.initState();
print('token: ${SimplePreferences.getToken()}');
if (SimplePreferences.getToken() == null){
Navigator.of(context).pushReplacementNamed('/Auth');
}
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
var provider = Provider.of<MyManager>(context, listen: false);
provider.setAll(context); //this function calls all my API calls, but for testing, I commented out all other functions and kept only the one written above
});
}
I don't quite understand why though. If someone could explain it to me, I'd be very happy
Use Consumer to access the Provider's Variable
return Consumer<YourProviderName>(builder : (context, value, child){
return value.isLoading? const Center(
child: CircularProgressIndicator(),
):YourWidget(),
});
My use case: I show a search UI the user can pick an item with - when the user taps an item, something async happens and depending on the result, close (https://api.flutter.dev/flutter/material/SearchDelegate/close.html) is called with either the picked item or a null. In my snippet below the async something is simply asking the user for confirmation with an AlertDialog.
This works without an issue but I updated the lint rules recently, and turned this one on: https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html. Now the linter complains about the BuildContext being used after an await in the call to close. Here is the snippet (full reproducible sample below, written with Flutter 2.10.4):
onTap: () async {
final confirmed = await _confirm(context, item) ?? false;
// Triggers 'Do not use BuildContexts across async gaps.'
// https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html
close(context, confirmed ? item : null);
},
I guess this makes sense and could be dangerous in some scenarios, so I'd better fix it. My question is: how do I implement my use case the 'right' way? One solution that works is to extract suggestions to a separate StatefulWidget and guard the call with a isMounted check, but this has the following drawbacks:
It requires a separate StatefulWidget that I would otherwise not need (boilerplate I don't want).
I need to pass a callback to the widget to call close (it belongs to SearchDelegate but now it will be called by code in the widget).
As close requires a BuildContext, I either have to pass the one that the SearchDelegate has to the widget to use (yuck) or just use the one from my widget - in this case it works (as close just uses Navigator.of(context)), but what if it were necessary to pass exactly the one from SearchDelegate?
Full code:
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: _Home(),
);
}
}
class _Home extends StatefulWidget {
#override
State<_Home> createState() => _HomeState();
}
class _HomeState extends State<_Home> {
String? _picked;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
TextButton(
onPressed: () => _maybePick(context),
child: const Text('Maybe pick'),
),
Center(
child: Text(_picked != null ? 'Last pick: $_picked' : 'No pick'),
),
],
),
);
}
Future<void> _maybePick(BuildContext context) async {
final result = await showSearch<String?>(
context: context,
delegate: _PickerDelegate(),
);
if (result != null) {
setState(() {
_picked = result;
});
}
}
}
class _PickerDelegate extends SearchDelegate<String?> {
final _allItems = List.generate(200, (index) => 'Item $index');
#override
Widget buildLeading(BuildContext context) {
return IconButton(
onPressed: () => close(context, null),
icon: const BackButtonIcon(),
);
}
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
onPressed: () => query = '',
icon: const Icon(Icons.clear),
),
];
}
#override
Widget buildSuggestions(BuildContext context) {
final items = _allItems.where((element) => element.contains(query));
return ListView(
children: items.map((item) {
return ListTile(
title: Text(item),
onTap: () async {
final confirmed = await _confirm(context, item) ?? false;
// Triggers 'Do not use BuildContexts across async gaps.'
// https://dart-lang.github.io/linter/lints/use_build_context_synchronously.html
close(context, confirmed ? item : null);
},
);
}).toList(growable: false),
);
}
#override
Widget buildResults(BuildContext context) {
// Keep it simple for the snippet.
throw UnimplementedError('results are not supported');
}
Future<bool?> _confirm(BuildContext context, String item) async {
return showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
content: Text("Pick '$item'?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: const Text('Yes'),
),
TextButton(
onPressed: () => Navigator.of(context).pop(false),
child: const Text('No'),
),
],
);
},
);
}
}
For retrieve items from FireStore and for pick image i am using cubit.
Cubit:
class ItemCubit extends Cubit<ItemState> {
ItemCubit(this._dataBase)
: super(ItemInitial());
final DataBase _dataBase;
StreamSubscription streamSubscription;
Future<void> pickItemImg() async {
final currentTempImg =
await ImagePickerWeb.getImage(outputType: ImageType.bytes);
emit(ItemImgPicked(currentTempImg));
}
Future getItem() async {
streamSubscription = _dataBase.getItem().listen((data) {
emit(ItemLoaded(data));
});
}
}
State:
#immutable
abstract class ItemState {}
class ItemLoaded extends ItemState {
final List<Item> item;
ItemLoaded(this.item);
}
class ItemImgPicked extends ItemState {
final Uint8List currentTempImg;
ItemImgPicked(this.currentTempImg);
}
Page with blocbuilders
class Page extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
RaisedButton(
onPressed: () async {
showDialog(
context: context,
builder: (BuildContext context) => Dialog(
child: Container(
width: 400,
child: OutlineButton(
onPressed: () async {
context.bloc<ItemCubit>().pickProductImg();
},
child: BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemImgPicked) {
return Image.memory(state.currentTempImg);
} else {
return Container();
}
},
),
),
),
),
);
},
child: Text('add'),
),
BlocBuilder<ItemCubit, ItemState>(
builder: (context, state) {
if (state is ItemLoaded) {
return Column(
children: state.item.map(
(item) {
return Text(item.name);
},
).toList(),
);
}
return CircularProgressIndicator();
},
)
],
),
);
}
}
Issue is when on show dialog I picked image, the picked image is displayed, but at the same time on main page blocbuilder for item list return CircularProgressIndicator. if I use hot reload at this time, after it shows me the list of item. It looks like the state for picked image replace state for item list. How to solve it?
Your main page bloc builder listens for ItemLoaded which you never emit as far as I can tell. You can put a breakpoint into that line, it should not get hit.
That said, please treat your async functions better, you missed to await some futures, that might not be your problem now, but it will become a problem sooner or later.
Let me explain first.
I have three screens in my app. These are MyHome.dart, OtherHome.dart and Selector.dart.
I want to launch Selector screen on the initial launch. In the Selector screen, there are two options to users. One is MyHome and another is OtherHome. After the first launch, the app will always open the last selected screen by the user on the first launch. What will be the right code for this?
Main.dart:
import 'selector.dart';
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(
home: Selector(),
));
}
Selector.dart:
import 'package:device_monitor/home.dart';
import 'package:flutter/material.dart';
import 'home.dart';
import 'myhome.dart';
class Selector extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyHome()),
);
},
child: Text('My Device'),
),
SizedBox(height: 30),
RaisedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Home()),
);
},
child: Text('Others Device'),
),
],
),
);
}
}
Here a code that can help you:
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() {
runApp(MaterialApp(
home: Selector(),
));
}
You need to get sharedpreferences package, here's a link
class Selector extends StatefulWidget {
#override
_SelectorState createState() => _SelectorState();
}
class _SelectorState extends State<Selector> {
bool pageReady = false;
/// This checks the whether page has been selected earlier,
/// should be placed in an initstate function
_checkPages() async {
SharedPreferences local = await SharedPreferences.getInstance();
if(local.getString('page-selected') != null){
if(local.getString('page-selected') == "1"){
//navigate to MyHome
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyHome()),
);
} else {
//Navigate to Home
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Home()),
);
}
} else {
setState(() {
pageReady = true;
});
}
}
#override
void initState() {
// TODO: implement initState
super.initState();
_checkPages();
}
savePage(String type) async {
if(type == "1"){
SharedPreferences local = await SharedPreferences.getInstance();
local.setString('page-selected', type);
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MyHome()),
);
} else {
SharedPreferences local = await SharedPreferences.getInstance();
local.setString('page-selected', type);
Navigator.push(
context,
MaterialPageRoute(builder: ( context ) => Home()),
);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: pageReady ? Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () {
savePage("1");
},
child: Text('My Device'),
),
SizedBox(height: 30),
RaisedButton(
onPressed: () {
savePage("2");
},
child: Text('Others Device'),
),
],
) : Center(child: CircularProgressIndicator()),
);
}
}
class MyHome extends StatefulWidget {
#override
_MyHomeState createState() => _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
#override
Widget build(BuildContext context) {
return Container();
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container();
}
So, I changed Selector() to a stateful widget and used an initState() to check if the user already selected a page previously, if yes, it routes the user to that page else it opens the selector page and once the user selects a page I save the page in session also with the savePage() function.
I have create a Alert Dialog for OTP Verification after verifying OTP I close it and then I had created a another dialog which is for data processing... and then I close it.
Result:-
First OTP Dialog closed after OTP verification by calling Navigator.of(context).pop(); and then second dialog just pops up but It does not closed after calling Navigator.of(context).pop();
What I want to do:
Close OTP Dialog after verifying OTP (Works)
Open Progress dialog (Works)
Close it after uploading profile in firebase storage (Does not Works)
Please help me solve this issue.
Thanks in Advance !
You probably forgetting await somewhere in your code.
Try this,
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
TextEditingController _otpCtrl = TextEditingController();
void dispose() {
_otpCtrl.dispose();
super.dispose();
}
Future<void> _verifyOTP() async {
final String otp = await _inputOtp();
String otpValidationError;
if (otp != null) otpValidationError = await _sendOtpVerifyRequest();
print(otpValidationError);
}
Future<String> _sendOtpVerifyRequest() async {
showDialog(
context: context,
builder: (context) {
return Center(child: CircularProgressIndicator());
},
);
await Future.delayed(Duration(seconds: 2)); //TODO: Do post request here
Navigator.pop(context);
return null;
}
Future<String> _inputOtp() async {
final flag = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: Text("Enter OTP"),
content: TextField(
controller: _otpCtrl,
decoration: InputDecoration(
hintText: "x x x x x x",
),
),
actions: <Widget>[
FlatButton(
child: Text("Cancel"),
onPressed: () {
Navigator.pop(context, false);
},
),
FlatButton(
child: Text("Confirm"),
onPressed: () {
Navigator.pop(context, true);
},
),
],
);
},
);
if (flag == true)
return _otpCtrl.text;
else
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: _verifyOTP,
child: Text("Click Here"),
),
),
);
}
}