Change route with Swipe - dart

I want to change a window with a simple swipe to the left, I have to 2 windows and when user swipes to the right side, I want to change my route.
I'm working with Named Routes.
void main() => runApp(new HeatMapApp());
class HeatMapApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'HeatMap',
initialRoute: '/',
routes: {
'/': (context) => new Main(),
'/home': (context) => new Home()
},
theme: new ThemeData(
primaryColor: Colors.black
)
);
}
}
This is my code in my App, the Main file doesn't have too much data now, I want to know the swipe event to redirect to 'home' path.
Main.dart
class Main extends StatelessWidget {
final bool _isLoggedIn = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: _isLoggedIn ? AppBar (
title: Text('Logged In')
) : null,
body: Center(
child: Text('Hello World!')
)
);
}
}

I think Dismissible Widget fits perfect on your requirement
class Main extends StatelessWidget {
final bool _isLoggedIn = true;
_nextPage(BuildContext context) async {
Navigator.of(context).pushReplacementNamed("/home");
}
#override
Widget build(BuildContext context) {
return Dismissible(
key: new ValueKey("dismiss_key"),
direction: DismissDirection.endToStart,
child: Scaffold(
appBar: _isLoggedIn ? AppBar(title: Text('Logged In')) : null,
body: Center(child: Text('Hello World!'))),
onDismissed: (direction) {
if (direction == DismissDirection.endToStart) {
_nextPage(context);
}
});
}
}

Related

How to use popUntil properly to reach the root of the stack?

I have pushed to two screen and wish to go back to my main home page. I tried doing that by using popUntil however it is not giving me the req result and just showing a black screen. Do i need to set a new route to my main page , even though i don't want to create a new instance of it ?
My code:
class Completed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Completed Screen',
home: CompleteScreen(),
routes: <String, WidgetBuilder>{
// "/my-app": (BuildContext context) => MyApp()
}
);
}
}
class CompleteScreen extends StatelessWidget{
#override
Widget build(BuildContext context){
Container Complete = Container(
child: Column(
.....
FlatButton(
onPressed: (){
Navigator.popUntil(
context,
ModalRoute.withName('/'),
);
// Navigator.popUntil(context, ModalRoute.withName(Navigator.defaultRouteName));
},
),
],
));
return Scaffold(
body: Complete
);
}
}
My main page
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: DefaultTabController(length: 2,child: MyHomePage(title: '')),
routes: <String, WidgetBuilder>{
"/TaskScreen": (BuildContext context) => new task(),
}
);
}
}
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
Widget build(BuildContext context){
final list = ListView.builder(
itemBuilder: (context, position) {
return Ink(
child: InkWell(
onTap: (){
Navigator.of(context).pushNamed("/TaskScreen");
},
child: Card(
...
),),); },);
return Scaffold(
...
}
}
I tried using '/TaskScreen' and '/my-app' however even that didn't work.
You could try this
Navigator.popUntil(
context,
ModalRoute.withName(
Navigator.defaultRouteName,
),
),
As defaultRouteName works as the first screen opened when the app starts.
EDIT
So, as mentioned below, named routes won't work with Navigator.defaultRouteNamenor route.isFirst, the best approach to solve this I've found is declaring all your routes in the main page, as these will become global (or that's what I understood), so your code would end something like this
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'My App',
home: DefaultTabController(length: 2,child: MyHomePage(title: '')),
routes: <String, WidgetBuilder>{
"/": (BuildContext context) => MyApp(), (or MyHomePage())
"/TaskScreen": (BuildContext context) => new task(),
}
);
}
}
With that done, anytime you want to go back to the main page you just have to call
Navigator.popUntil(context, ModalRoute.withName('/'));
Hope that works for you.
The route in the popUntil has a property called isFirst that returns true if the route is the first route in the navigator. So in your case, you can use something like:
Navigator.of(context).popUntil((route) {
return route.isFirst;
});

How to change title of main.dart AppBar in it's child programmatically?

I have an AppBar in main.dart and I want to defined it as primary on it's child, But I want to change the title of AppBar itself when I'm on child's page, how can i do that properly?
void main() => runApp(MyApp());
class MyApp extends StatelessWidget{
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Flutter App",
theme: ThemeData(
primaryColor: Colors.cyan,
brightness: Brightness.dark
),
home: Scaffold(
appBar: AppBar(
title: Text("Main Dart"),
),
body: HomeScreen(),
),
routes: <String, WidgetBuilder>{
'/homeScreen': (buildContext)=>HomeScreen(),
'/second': (buildContext)=>Second()
},
);
}
}
//HomeScreen or Second Widget on different dart file
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
//here I want to change the title of Main Dart to HomeScreen
return Container(
child: Center(
child: FlatButton(
child: new Text("Home screen"),
onPressed: (){
Route route = MaterialPageRoute(builder: (context) => Second());
Navigator.push(context, route);
},
),
),
);
}
}
or I need to put Scaffold(appBar:AppBar(...), ...) in every screen? it is the best approach?
Have a BLoC for app properties in app_properties_bloc.dart
final appBloc = AppPropertiesBloc();
class AppPropertiesBloc{
StreamController<String> _title = StreamController<String>();
Stream<String> get titleStream => _title.stream;
updateTitle(String newTitle){
_title.sink.add(newTitle);
}
dispose() {
_title.close();
}
}
Use stream builder in AppBar like this:
AppBar(
title: StreamBuilder<Object>(
stream: appBloc.titleStream,
initialData: "Main Dart",
builder: (context, snapshot) {
return Text(snapshot.data);
}
),
),
Use this to update title on button's onPressed()
onPressed: () {
appBloc.updateTitle('new title');
},
Just in case you are changing only the title of Scaffold then this will work.
I am creating a DefaultScaffold with the title each screen provides. Here the code will show the MainPage and two other pages which have the same AppBar with changed titles.
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(initialRoute: 'home', routes: <String, WidgetBuilder>{
'home': (context) => SOMain(),
'/secondPage': (context) => DefaultScaffold("Second Screen", SOSecond()),
'/thirdPage': (context) => DefaultScaffold("Third Screen", SOThird()),
});
}
}
class DefaultScaffold extends StatelessWidget {
String title;
Widget body;
DefaultScaffold(this.title, this.body);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: body,
);
}
}
class SOMain extends StatelessWidget {
#override
Widget build(BuildContext context) {
return DefaultScaffold(
"Main Screen",
Center(
child: RaisedButton(
child: Text("Go to second screen"),
onPressed: () {
Navigator.pushNamed(context, '/secondPage');
}),
),
);
}
}
class SOSecond extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Go the 3rd screen"),
onPressed: () => Navigator.pushNamed(context, "/thirdPage"),
),
);
}
}
class SOThird extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(child: Text("You are on last screen"));
}
}
Note: This is a simple workaround and may not be the best way to do this.
You can accomplish updating the state of the parent from a child by using a callback function.
Parent Class:
import 'package:flutter/material.dart';
class Parent extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return ParentState();
}
}
class ParentState extends State<Parent> {
String title = "Old Title";
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(title),
),
body: DaysFragmentView(onTitleSelect: (String value) {
setTitle(value);
}
),
);
}
void setTitle(String value) {
setState(() {
title = value;
});
}
}
Child Class
typedef TitleCallback = void Function(Title color);
class DaysFragmentView extends StatelessWidget {
const DaysFragmentView({this.onTitleSelect});
final TitleCallback onTitleSelect;
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
RaisedButton(
child: Text('One'),
onPressed: () {
onTitleSelect("TITLE ONE");
},
),
RaisedButton(
child: Text('Two'),
onPressed: () {
onTitleSelect("TITLE TWO");
},
)
],
);
}
}
Reference:
call-method-in-one-stateful-widget-from-another-stateful-widget-flutter
working-with-callback-in-flutter
Using ValueListenableBuilder is an option.
Use an instance variable
String appTitle;
Then set the app bar as in the following block:
appBar: AppBar(
ValueListenableBuilder<String>(
valueListenable: appTitle,
builder: (context, value, child) {
return Text(appTitle.value);
},
),
After that you can simply set appTitle.value in the other class. The title will be changed too because it listens to that value.
appTitle.value = "Home Screen";
Some answer here are too complicated. Here is a full working example using app bar update from child with scafold widget.
You can run the example in dart pad
import 'package:flutter/material.dart';
void main() {
runApp(const MyHomePage(title: 'init title'));
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
final ValueNotifier<String?> _appBarTitleNotifier = ValueNotifier<String?>(null);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: ValueListenableBuilder<String?>(
builder: (BuildContext context, String? value, Widget? child) {
return Text(value ?? widget.title);
},
valueListenable: _appBarTitleNotifier,
),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ChildDemoTitleBar(titleNotifier: _appBarTitleNotifier)
],
),
),
),
);
}
}
class ChildDemoTitleBar extends StatefulWidget {
final ValueNotifier<String?> titleNotifier;
const ChildDemoTitleBar({Key? key, required this.titleNotifier})
: super(key: key);
#override
State<ChildDemoTitleBar> createState() => _ChildDemoTitleBarState();
}
class _ChildDemoTitleBarState extends State<ChildDemoTitleBar> {
int _counter = 0;
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 0, 20, 20),
child: InkWell(
onTap: () {
_counter++;
widget.titleNotifier.value = "title updated $_counter";
},
child: const Text("tap to update title")));
}
}

Losing data while navigating screens in Flutter

I am new to Flutter and just started to make a tiny little app which takes a list of Top Movies from a server using an async request. and when I tap on top of each one of list items, then it navigates me to another screen to show some details about the movie.
But there is a problem, when I tap on any item to see it's details, inside the details page, when I press back, in the first page, it just loads data again which is not a good user experience. also uses more battery and bandwidth for each request.
I don't know if this is a natural behavior of Flutter to lose data of a Stateful widget after navigating to another screen or there is something wrong with my code.
Can anybody help me with this
This is my code:
import "package:flutter/material.dart";
import "dart:async";
import "dart:convert";
import "package:http/http.dart" as http;
void main() {
runApp(MovieApp());
}
class MovieApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'test',
home: Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
Text("Top Movies List",
textDirection: TextDirection.rtl,
style: TextStyle(color: Colors.black87))
]
)
),
body: MoviesList()
)
);
}
}
class MoviesList extends StatefulWidget {
#override
MoviesListState createState() => new MoviesListState();
}
class MoviesListState extends State<MoviesList> {
List moviesList = [];
Future<Map> getData() async {
http.Response response = await http.get(
'http://api.themoviedb.org/3/discover/movie?api_key={api_key}'
);
setState(() {
moviesList = json.decode(response.body)['results'];
});
// return json.decode(response.body);
}
#override
Widget build(BuildContext context) {
getData();
if(moviesList == null) {
return Scaffold(
body: Text('Getting data from server')
);
} else {
return ListView.builder(
itemCount: moviesList.length,
itemBuilder: (context, index){
return Container(
child: ListTile(
title: Text(moviesList[index]['title']),
onTap: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => MovieDetails()),
);
}
)
);
}
);
}
}
}
class MovieDetails extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Details')
),
body: Container(
child: Center(
child: RaisedButton(
onPressed: () {
Navigator.pop(context);
},
child: Text('Go back!'),
),
)
),
);
}
}
Move your getData() method inside the initState() in your State class.
(Remove it from build method)
#override
void initState() {
getData();
super.initState();
}

setState doesn't update the interface

I'm trying to learn flutter but his has been in my way for over a week, I'm not able to get setState to work properly.
In this case I want to press a button and change its icon and properties, basically having another element but I just can't get it to work.
Here's my code for the widget:
import 'package:flutter/material.dart';
class UserButton extends StatefulWidget {
#override
_UserButtonState createState() => _UserButtonState();
}
class _UserButtonState extends State<UserButton> {
#override
Widget build(BuildContext context) {
bool loggedin = false;
return Container(
child: loggedin
? IconButton(
onPressed: () {
setState(() {
loggedin = false;
});
},
icon: Icon(Icons.person),
)
: IconButton(
onPressed: () {
setState(() {
loggedin = true;
});
},
icon: Icon(Icons.person_outline),
tooltip: "Login",
));
}
}
And here is the main app code:
import 'package:flutter/material.dart';
import 'package:orar/user_button.dart';
main(List<String> args) {
runApp(Home());
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme:
ThemeData(primaryColor: Colors.cyan, accentColor: Colors.cyanAccent),
home: Scaffold(
appBar: AppBar(
title: Text("test"),
actions: <Widget>[UserButton()],
),
body: ListView(
children: <Widget>[],
),
),
);
}
}
loggedin should be state variable. In your case it is local variable inside build method.
Move loggedin out of build method and it should work

Flutter Snackbar dismiss listener

I was looking for a way to check if the Snackbar has been dismissed, either by the user or by the timeout stuff. I could't really get any listener of doing it.
This is what I got so far,
Scaffold.of(context)
.showSnackBar(SnackBar(content: Text("Title")))
.closed
.then((reason) {
// snackbar is now closed
});
This is the one way around, I was looking for exact listener. I don't want any work around, like setting duration of Snackbar and then listening to it after the duration has passed.
see full example below
I just wrapped SnackBar content with WillPopoScope and if the user pressed back button it will remove snackbar.
By default it will specify SnackBarClosedReason.remove reason
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body: FirstPage(),
),
);
}
}
class FirstPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: ElevatedButton(
child: Text('go to test page'),
onPressed: () => Navigator.of(context).push(MaterialPageRoute(builder: (context) => Test())),
),
);
}
}
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
child: Text('show snack'),
onPressed: () => _showSnack(context),
),
),
);
}
void _showSnack(BuildContext context) {
ScaffoldMessenger.of(context)
.showSnackBar(
SnackBar(
content: WillPopScope(
onWillPop: () async {
ScaffoldMessenger.of(context).removeCurrentSnackBar();
return true;
},
child: Text("Title"),
),
),
)
.closed
.then((reason) {
print('------------ $reason');
});
}
}

Resources