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.
Related
i am trying to create a login page so that when i am logged-in, the Navigationrail Widget lets me access all its destinations. When logged off i can only access two pages.
I am using Provider in login.dart to triger a widget rebuild in main.dart .
here is the code.
login.dart
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:httptest/depand/XmlLogin.dart';
import 'package:httptest/main.dart';
void login(String userName, String passWord) async {
Response response;
Dio dio = Dio();
dio.interceptors.add(InterceptorsWrapper(
onResponse: (response, handler) {
var token = getToken.Transcribe(response.data);
LoginProvider obj = LoginProvider();
obj.providestate(true);
print(token);
print("logged in");
handler.next(response);
},
));
try {
//Http Post method
} catch (e) {
print(e);
}
}
class LoginProvider extends ChangeNotifier {
bool loginstate = false;
void providestate(bool val) {
loginstate = val;
print("loginstate changed to $loginstate");
notifyListeners();
}
}
main.dart
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => MyHomePageState();
}
class MyHomePageState extends State<MyHomePage> {
var selectedIndex = 0;
List<Widget> pages = [Page0(), Page1(), Page2(), Placeholder()];
#override
Widget build(BuildContext context) {
return LayoutBuilder(builder: (context, constraints) {
return Scaffold(
body: Row(
children: [
SafeArea(
child: ChangeNotifierProvider<LoginProvider>(
create: (context) => LoginProvider(),
child: Builder(
builder: (context) {
return Consumer<LoginProvider>(
builder: (context, provider, child) {
return NavigationRail(
extended: constraints.maxWidth >= 600,
minExtendedWidth: 200,
destinations: [
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination(),
NavigationRailDestination()
],
selectedIndex: selectedIndex,
onDestinationSelected: (value) {
if (provider.loginstate) {
setState(() {
selectedIndex = value;
});
print("On");
} else {
if (value == 0 || value == 3) {
setState(() {
selectedIndex = value;
});
print("OFF");
}
}
},
);
});
},
),
)),
Expanded(
child: Scaffold(
body: IndexedStack(
index: selectedIndex,
children: pages,
),
),
),
],
),
);
});
}
}
the login goes through but i Still cant access pages 1 and 2.
it prints out 'loginstate changed to True' from login.dart.
To ensure access place a provider in a widget tree something like this:
void main() {
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => LoginProvider()),
...
],
child: const MyApp(),
),
);
}
Then in child widgets access it using either Consumer to listen changes or Provider.of when just calling a method.
Consumer<LoginProvider>(
builder: (context, provider, child) {
...
},
)
Provider.of<LoginProvider>(context, listen: false).login(userName, passWord);
See Simple app state management for the full example.
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'),
),
],
);
},
);
}
}
In appbar I am trying to show profile icon after logged. When app start, appbar show profile icon, but at the same time in debug console give me an error 'A build function returned null'. When open profile page and return back, still the same error 'returned null' How to solve it?
class TestApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (context) => AuthBloc(
authService: AuthService())
..add(
AppStart(),
),
),
],
child: MaterialApp(
home: HomePage(),
),
);
}
}
homepage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
actions: <Widget>[
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return profileIcon(context);
} else if (state is UnAuthenticated) {
return logIn(context);
}
},
),
],
),
);
}
}
bloc
#override
AuthState get initialState => AuthState();
#override
Stream<AuthState> mapEventToState(AuthEvent event) async* {
if (event is AppStart) {
try {
final user = await AuthService.getCurrentUser();
yield Authenticated(user: user);
} catch (e) {
yield UnAuthenticated();
}
}
}
icon:
Widget profileIcon(context) {
return Row(
children: <Widget>[
FlatButton.icon(
icon: Icon(),
label: Text(
'Profile',
),
onPressed: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => UserProfile()));
},
),
],
);
}
state:
class Authenticated extends AuthState {
final FirebaseUser user;
Authenticated({this.user});
#override
List<Object> get props => [user];
}
class UnAuthenticated extends AuthState {
#override
List<Object> get props => [];
}
I'm not really sure but my guess is you have another state for your initialState which is not handled in the BlocBuilder. Even though you add AppStart event right after providing AuthBloc which will end up with either Authenticated or UnAuthenticated state, you still need to put another else for your initialState. Even if it's not the case, try to add an else statement
appBar: AppBar(
actions: <Widget>[
BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Authenticated) {
return profileIcon(context);
} else if (state is UnAuthenticated) {
return logIn(context);
} else {
return Container();
}
},
),
],
),
In the code below, I am reading a pdf file from my images folder then viewing it in a new page. But when I navigate to the new page it takes time to load the pdf. So, I want to add a CircularProgressIndicator while transitioning. I can't figure out how to do it. How do I add a CircularProgressIndicator widget?
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_full_pdf_viewer/flutter_full_pdf_viewer.dart';
class PdfPage extends StatefulWidget {
#override
_PdfPageState createState() => _PdfPageState();
}
class _PdfPageState extends State<PdfPage> {
Future<String> makeFile() async {
Directory tempDir = await getTemporaryDirectory();
String tempPath = tempDir.path;
File tempFile = File('$tempPath/copy.pdf');
ByteData bd = await rootBundle.load('images/dummy.pdf');
await tempFile.writeAsBytes(bd.buffer.asUint8List(), flush: true);
return tempFile.path;
}
bool isLoading = false;
#override
void initState() {
// TODO: implement initState
super.initState();
makeFile().then((value) {
setState(() {
path = value;
print(path);
//isLoading = false;
});
});
}
String path;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
child: Text("Open PDF Screen"),
onPressed: () {
// CircularProgressIndicator(value: true,);
Navigator.push(context,
MaterialPageRoute(builder: (context) => PDFScreen(path)));
},
),
),
);
}
}
class PDFScreen extends StatelessWidget {
final String path;
// final bool isLoading;
PDFScreen(
this.path,
);
#override
Widget build(BuildContext context) {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Scaffold PDF"),
),
path: path);
}
}
You can use FutureBuilder class which is precisely made for this. That is, some action is expected in the future and you would like to show a progress indicator meanwhile. The action may actually complete or return an error. Futurebuilder lets you handle all these scenarios.
You can shift makefile() into class PDFScreen and use widget build method to return the FutureBuilder. The simplest implementation is below.
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: makeFile(),
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
} else {
return PDFViewerScaffold(
appBar: AppBar(
title: Text("Scaffold PDF"),
),
path: snapshot.data
);
}
},
);
}
You might want to visit the official docuementation or this blog on Medium.
One way is to use it in an Dialog.
static Future showDialog(BuildContext context, GlobalKey _key) async {
return showLoadingDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context){
return SimpleDialog(
key: _key,
children: <Widget>[
Center(
child: Container(
child: Row(
children: <Widget>[
CircularProgressIndicator(),
SizedBox(
height:10,
width:10,
),
Text("Please Wait!"),
]
),
],
),
);
}
);
}
Here GlobalKey is used to Show or hide the dialog created.
To call this simple initialize a GlobalKey in the class as GlobalKey<State> _dialogKey = GlobalKey<State>();.
Then you can show the dialog as:
showLoadingDialog(context, _dialogKey);
And when you want it to hide, just call:
Navigator.of(_dialogKey.currentContext,rootNavigator: true).pop();
This is just one way of using CircularProgressIndicator in an SimpleDialog, Hope this helps.
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"),
),
),
);
}
}