I have image shown using one of Flutter widgets
Image.network(....);
I want to add functionality that after tapping on image I can present this image in fullscreen mode. How it can be done?
You say you want something like in this flutter cookbook?
import 'package:flutter/material.dart';
void main() => runApp(HeroApp());
class HeroApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image/Detail Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/lakes/images/lake.jpg',
),
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen();
}));
},
),
);
}
}
class DetailScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/lakes/images/lake.jpg',
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
You can use the cache_network_image package to show the cached image without download it again.
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
void main() => runApp(HeroApp());
class HeroApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image/Detail Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
child: Hero(
tag: 'imageHero',
child: CachedNetworkImage(
imageUrl: 'https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/lakes/images/lake.jpg',
placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
)
),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen();
}));
},
),
);
}
}
class DetailScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: 'imageHero',
child: CachedNetworkImage(
imageUrl: 'https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/lakes/images/lake.jpg',
placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
My final release (with real fullscreen):
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/services.dart';
void main() => runApp(HeroApp());
class HeroApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image/Detail Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: GestureDetector(
child: Hero(
tag: 'imageHero',
child: CachedNetworkImage(
imageUrl:
'https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/lakes/images/lake.jpg',
placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
)),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen();
}));
},
),
);
}
}
class DetailScreen extends StatefulWidget {
#override
_DetailScreenState createState() => _DetailScreenState();
}
class _DetailScreenState extends State<DetailScreen> {
#override
initState() {
SystemChrome.setEnabledSystemUIOverlays([]);
super.initState();
}
#override
void dispose() {
//SystemChrome.restoreSystemUIOverlays();
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: 'imageHero',
child: CachedNetworkImage(
imageUrl:
'https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/lakes/images/lake.jpg',
placeholder: new CircularProgressIndicator(),
errorWidget: new Icon(Icons.error),
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
Passing data from main to detail page
Just to complete my answer, I add some code showing how you could pass the image url from main to detail page.
import 'package:flutter/material.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/services.dart';
void main() => runApp(HeroApp());
class HeroApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Image/Detail Demo',
home: MainScreen(),
);
}
}
class MainScreen extends StatefulWidget {
#override
_MainScreenState createState() => _MainScreenState();
}
class _MainScreenState extends State<MainScreen> {
var _url = [
'https://raw.githubusercontent.com/flutter/website/master/src/_includes/code/layout/lakes/images/lake.jpg',
'https://github.com/flutter/plugins/raw/master/packages/video_player/doc/demo_ipod.gif?raw=true'
];
var _tag = ['imageHero', 'imageHero2'];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Main Screen'),
),
body: ListView(
children: <Widget>[
GestureDetector(
child: Hero(
tag: _tag[0],
child: CachedNetworkImage(
imageUrl: _url[0],
placeholder: Center(child: Container(width: 32, height: 32,child: new CircularProgressIndicator())),
errorWidget: new Icon(Icons.error),
)),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen(tag: _tag[0], url: _url[0]);
}));
},
),
GestureDetector(
child: Hero(
tag: _tag[1],
child: CachedNetworkImage(
imageUrl: _url[1],
placeholder: Center(child: Container(width: 32, height: 32,child: new CircularProgressIndicator())),
errorWidget: new Icon(Icons.error),
)),
onTap: () {
Navigator.push(context, MaterialPageRoute(builder: (_) {
return DetailScreen(tag: _tag[1], url: _url[1]);
}));
},
),
],
),
);
}
}
class DetailScreen extends StatefulWidget {
final String tag;
final String url;
DetailScreen({Key key, #required this.tag, #required this.url})
: assert(tag != null),
assert(url != null),
super(key: key);
#override
_DetailScreenState createState() => _DetailScreenState();
}
class _DetailScreenState extends State<DetailScreen> {
#override
initState() {
SystemChrome.setEnabledSystemUIOverlays([]);
super.initState();
}
#override
void dispose() {
//SystemChrome.restoreSystemUIOverlays();
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Center(
child: Hero(
tag: widget.tag,
child: CachedNetworkImage(
imageUrl: widget.url,
placeholder: Center(child: Container(width: 32, height: 32,child: new CircularProgressIndicator())),
errorWidget: new Icon(Icons.error),
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
UPDATE
In order to pop back tapping outside the image, bring outside the GestureDetector in the Detail widget.
class _DetailScreenState extends State<DetailScreen> {
#override
initState() {
SystemChrome.setEnabledSystemUIOverlays([]);
super.initState();
}
#override
void dispose() {
//SystemChrome.restoreSystemUIOverlays();
SystemChrome.setEnabledSystemUIOverlays(SystemUiOverlay.values);
super.dispose();
}
#override
Widget build(BuildContext context) {
return GestureDetector(
child: Scaffold (
body: Center(
child: Hero(
tag: widget.tag,
child: CachedNetworkImage(
imageUrl: widget.url,
placeholder: Center(child: Container(width: 32, height: 32,child: new CircularProgressIndicator())),
errorWidget: new Icon(Icons.error),
),
),
),
),
onTap: () {
Navigator.pop(context);
},
);
}
}
You can use this code to have full screen of image!
class DetailScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: GestureDetector(
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Hero(
tag: 'imageHero',
child: Image.network(
'https://picsum.photos/250?image=9',
),
),
),
onTap: () {
Navigator.pop(context);
},
),
);
}
}
You can easily use this package easy_image_viewer
It worked perfectly without having to create a new screen to handle full screen.
The package handles full screen, pinching, zooming in and out of image.
Check this sample code below.
GestureDetector(
onTap: () {
showImageViewer(context, Image.asset("asset/image/myimage.jpg").image,
swipeDismissible: false);
},
child: Container(
height: 150,
child: Image.asset(
"asset/image/myimage.jpg",
fit: BoxFit.cover,
),
),
)
The Image opens in full screen, pinchable and zoomable.
I simply found a library with just about what you are looking for and with animated Hero effects.
full_screen_image by furkan.kayali#bil.omu.edu.tr
FullScreenWidget(
child: Hero(
tag: "customTag",
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: Image.asset(
"assets/image2.jpg",
fit: BoxFit.cover,
),
),
),
);
Use easy_image_viewer package. view
This example for Network image,
This package provides an easy-to-use and highly customizable image viewer. It allows you to display images in a full-screen view with zooming, panning, and rotation capabilities.
#Amfstacks shows how this works for image which is in your assets. This example for Network image,
GestureDetector(
onTap: () async {
final imageProvider =
Image.network("your url").image;
showImageViewer(context, imageProvider,
onViewerDismissed: () {
print("dismissed");
});
},
child: Container(
height: 150,
child: Image(
image: NetworkImage("your url"),
)),
),
Related
Hi I need some help with passing an image that is selected from my gallery to another screen.
When I go to the 'Pick Image' screen through the grid-tiled screen, I can select an image from my gallery app. As soon as I pick one, the image pops up on the 'Pick Image' screen and it should be passed to the previous screen(grid tiled) so the image can be displayed in a grid tile.
Anyone could handle this?
The relevant part of the code is right below.
Grid Tile screen
Widget build(BuildContext context) {
return GridView.count(crossAxisCount: 4,
children: List.generate(lastDay, (index){
return GridTile(
child: Card(
child: Column(
children: <Widget>[
Text('Day ' '$index'),
SizedBox(height: 20.0,),
IconButton(
icon: Icon(Icons.add),
onPressed: (){
Navigator.push(context,
MaterialPageRoute(builder: (context) => PickImage()));
},),
],
),
),
);
}),
);
}
Pick Image Screen
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
class PickImage extends StatefulWidget {
PickImage() : super();
final String title = "Pick Image";
#override
_PickImageState createState() => _PickImageState();
}
class _PickImageState extends State<PickImage> {
Future<File> imageFile;
pickImageFromGallery(ImageSource source) {
setState(() {
imageFile = ImagePicker.pickImage(source: source);
});
}
Widget showImage() {
return FutureBuilder<File>(
future: imageFile,
builder: (BuildContext context, AsyncSnapshot<File> snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.data != null) {
return Image.file(
snapshot.data,
width: 400,
height: 400,
);
} else if (snapshot.error != null) {
return const Text(
'Error Picking Image',
textAlign: TextAlign.center,
);
} else {
return const Text(
'No Image Selected',
textAlign: TextAlign.center,
);
}
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showImage(),
RaisedButton(
child: Text("Select Image from Gallery"),
onPressed: () {
pickImageFromGallery(ImageSource.gallery);
},
),
],
),
),
);
}
}
You can copy paste run full code below
Step 1: Use Navigator.pop to return image
RaisedButton(
onPressed: () {
Navigator.pop(context, imageFileReturn);
},
child: Text('Selecct Finish, Go back '),
),
Step 2: Use Map<int, File> keep related index and image
Map<int, File> imageFileMap = {};
IconButton(
icon: Icon(Icons.add),
onPressed: () async {
imageFile = await Navigator.push(context,
MaterialPageRoute(builder: (context) => PickImage()));
imageFileMap[index] = imageFile;
setState(() {});
},
Step 3: Show image in Map
SizedBox(
height: 20.0,
child: imageFileMap[index] != null
? Image.file(
imageFileMap[index],
)
: Container()),
working demo
full code
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'dart:io';
class PickImage extends StatefulWidget {
PickImage() : super();
final String title = "Pick Image";
#override
_PickImageState createState() => _PickImageState();
}
class _PickImageState extends State<PickImage> {
Future<File> imageFile;
File imageFileReturn;
pickImageFromGallery(ImageSource source) {
setState(() {
imageFile = ImagePicker.pickImage(source: source);
});
}
Widget showImage() {
return FutureBuilder<File>(
future: imageFile,
builder: (BuildContext context, AsyncSnapshot<File> snapshot) {
imageFileReturn = snapshot.data;
if (snapshot.connectionState == ConnectionState.done &&
snapshot.data != null) {
return Image.file(
snapshot.data,
width: 400,
height: 400,
);
} else if (snapshot.error != null) {
return const Text(
'Error Picking Image',
textAlign: TextAlign.center,
);
} else {
return const Text(
'No Image Selected',
textAlign: TextAlign.center,
);
}
},
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showImage(),
RaisedButton(
child: Text("Select Image from Gallery"),
onPressed: () {
pickImageFromGallery(ImageSource.gallery);
},
),
RaisedButton(
onPressed: () {
Navigator.pop(context, imageFileReturn);
},
child: Text('Selecct Finish, Go back '),
),
],
),
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Test(),
);
}
}
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
int lastDay = 30;
Map<int, File> imageFileMap = {};
File imageFile;
#override
Widget build(BuildContext context) {
return GridView.count(
crossAxisCount: 4,
children: List.generate(lastDay, (index) {
return GridTile(
child: Card(
child: Column(
children: <Widget>[
Text('Day ' '$index'),
SizedBox(
height: 20.0,
child: imageFileMap[index] != null
? Image.file(
imageFileMap[index],
)
: Container()),
IconButton(
icon: Icon(Icons.add),
onPressed: () async {
imageFile = await Navigator.push(context,
MaterialPageRoute(builder: (context) => PickImage()));
imageFileMap[index] = imageFile;
setState(() {});
},
),
],
),
),
);
}),
);
}
}
I have a class for each "page" of my app, and they all share the same appbar, which is a class of its own. I have a button on the appbar, and I want it to call a function from whichever page is open. The function is on each page, and it has the same name on each page.
In my shortened code below, you see the shared MyAppBar, and you see 2 pages. Each of those pages uses MyAppBar, and each of those pages has _myFunction().
How can I call _myFunction() for each the current page from MyAppBar?
class MyAppBar {
setAppBar(context, String title) {
return new AppBar(
backgroundColor: Colors.blue,
title: Text(
title,
style: TextStyle(color: Colors.white),
),
actions: <Widget>[
child: IconButton(myIcon),
onPressed: () => { this should call the current pages _myFunction},),
],
}
}
class _Page1State extends State<Page1>
{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar().setAppBar(context, 'Page 1'),
body: Container(some content here)
)
}
_myFunction()
{
do some stuff;
}
}
class _Page2State extends State<Page2>
{
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar().setAppBar(context, 'Page 2'),
body: Container(some content here)
)
}
_myFunction()
{
do some stuff;
}
}
You can just pass those functions.
import 'package:flutter/material.dart';
class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
MyAppBar({this.pageInstanceFunction});
var pageInstanceFunction;
#override
Widget build(BuildContext context) {
return AppBar(
backgroundColor: Colors.orange,
title: Text('My Custom AppBar for #page'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.ac_unit),
onPressed: () {
pageInstanceFunction();
},
),
],
);
}
#override
Size get preferredSize => Size.fromHeight(kToolbarHeight);
}
Here's my Page One
import 'package:flutter/material.dart';
import 'package:stackoverflow/MyAppBar.dart';
import 'package:stackoverflow/PageTwo.dart';
class PageOne extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(
pageInstanceFunction: sayHello,
),
body: Container(
color: Colors.deepOrange,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Center(
child: RaisedButton(
onPressed: () {
Navigator.push(
context, MaterialPageRoute(builder: (context) => PageTwo()));
},
child: Text('Page Two'),
),
),
),
);
}
void sayHello() {
print('Hello from PageOne');
}
}
And Page Two
import 'package:flutter/material.dart';
import 'package:stackoverflow/MyAppBar.dart';
class PageTwo extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(
pageInstanceFunction: sayHello,
),
body: Container(
color: Colors.lightBlueAccent,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: Text('Page Two'),
),
);
}
void sayHello() {
print('Hello from PageTwo');
}
}
I had followed the hero animation tutorial in flutter. When i tried to add one more sceen i just noticed that the timedilation property is affecting the loading time of other screens also. And I had tried resetting the variable to zero but it didn't work as expected.
class PhotoHero extends StatelessWidget {
const PhotoHero({Key key, this.photo, this.onTap, this.width})
: super(key: key);
final String photo;
final VoidCallback onTap;
final double width;
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.asset(
photo,
fit: BoxFit.contain,
),
),
),
),
); }}
class HeroAnimation extends StatelessWidget {
Widget build(BuildContext context) {
timeDilation = 10.0; // 1.0 means normal animation speed.
return Scaffold(
appBar: AppBar(
title: const Text('Basic Hero Animation'),
),
body: Center(
child: PhotoHero(
photo: 'images/flippers-alpha.png',
width: 300.0,
onTap: () {
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (context) => SecondScreen()));
},
),
),
); }}
void main() {
runApp(MaterialApp(
routes: {
'/': (context) => HeroAnimation(),
'/second': (context) => SecondScreen(),
'/third': (context) => ThirdScreen(),
}, ));}
class ThirdScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Title(color: Colors.red, child: Text('Dummy Title')),
),
); }}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: Container(
// Set background to blue to emphasize that it's a new route.
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: PhotoHero(
photo: 'images/flippers-alpha.png',
width: 100.0,
onTap: () {
Navigator.of(context).pushNamed('/third');
},
),
),
); }}
This is expected as timeDilation is global property sort of , so you need to set it every time you need to change the speed of your animation onTap will be perfect place to do so,
check the modified code below
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
class PhotoHero extends StatelessWidget {
const PhotoHero({Key key, this.photo, this.onTap, this.width})
: super(key: key);
final String photo;
final VoidCallback onTap;
final double width;
Widget build(BuildContext context) {
return SizedBox(
width: width,
child: Hero(
tag: photo,
child: Material(
color: Colors.transparent,
child: InkWell(
onTap: onTap,
child: Image.asset(
photo,
fit: BoxFit.contain,
),
),
),
),
); }}
class HeroAnimation extends StatelessWidget {
Widget build(BuildContext context) {
//timeDilation = 10.0; // 1.0 means normal animation speed.
return Scaffold(
appBar: AppBar(
title: const Text('Basic Hero Animation'),
),
body: Center(
child: PhotoHero(
photo: '/images/flippers-alpha.png',
width: 300.0,
onTap: () {
timeDilation = 10.0;
Navigator.of(context).push(
MaterialPageRoute<void>(builder: (context) => SecondScreen()));
},
),
),
); }}
void main() {
runApp(MaterialApp(
routes: {
'/': (context) => HeroAnimation(),
'/second': (context) => SecondScreen(),
'/third': (context) => ThirdScreen(),
}, ));}
class ThirdScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
timeDilation = 1.0;
return Scaffold(
appBar: AppBar(
title: Title(color: Colors.red, child: Text('Dummy Title')),
),
); }}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
//timeDilation = 1.0;
return Scaffold(
appBar: AppBar(
title: const Text('Flippers Page'),
),
body: Container(
// Set background to blue to emphasize that it's a new route.
color: Colors.lightBlueAccent,
padding: const EdgeInsets.all(16.0),
alignment: Alignment.topLeft,
child: PhotoHero(
photo: '/images/flippers-alpha.png',
width: 100.0,
onTap: () {
Navigator.of(context).pushNamed('/third');
},
),
),
); }}
If you add timeDilation it will affect the other screens animation time. Because it's a global property. In order to go back to the normal animation speed you have to change that variable value to 1.0 which is the normal animation speed.
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
timeDilation = 8.0; // Since we need the animation to slow down.
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
timeDilation = 1.0; // Changing the value to normal animation speed.
}
As a note, if you are coming back using back button the build method won't get called so the timeDilation value won't change to the value of the screen which you're in. In this case you've to make your screen as StatefulWidget then you can set the value on the life cycle methods.
I'm trying to start a new screen within an onTap but I get the following error:
Navigator operation requested with a context that does not include a
Navigator.
The code I am using to navigate is:
onTap: () { Navigator.of(context).pushNamed('/settings'); },
I have set up a route in my app as follows:
routes: <String, WidgetBuilder>{
'/settings': (BuildContext context) => new SettingsPage(),
},
I've tried to copy the code using the stocks sample application. I've looked at the Navigator and Route documentation and can't figure out how the context can be made to include a Navigator. The context being used in the onTap is referenced from the parameter passed into the build method:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
SettingsPage is a class as follows:
class SettingsPage extends Navigator {
Widget buildAppBar(BuildContext context) {
return new AppBar(
title: const Text('Settings')
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: buildAppBar(context),
);
}
}
TLDR: Wrap the widget which needs to access to Navigator into a Builder or extract that sub-tree into a class. And use the new BuildContext to access Navigator.
This error is unrelated to the destination. It happens because you used a context that doesn't contain a Navigator instance as parent.
How do I create a Navigator instance then ?
This is usually done by inserting in your widget tree a MaterialApp or WidgetsApp. Although you can do it manually by using Navigator directly but less recommended. Then, all children of such widget can access NavigatorState using Navigator.of(context).
Wait, I already have a MaterialApp/WidgetsApp !
That's most likely the case. But this error can still happens when you use a context that is a parent of MaterialApp/WidgetsApp.
This happens because when you do Navigator.of(context), it will start from the widget associated to the context used. And then go upward in the widget tree until it either find a Navigator or there's no more widget.
In the first case, everything is fine. In the second, it throws a
Navigator operation requested with a context that does not include a Navigator.
So, how do I fix it ?
First, let's reproduce this error :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
);
}
}
This example creates a button that attempts to go to '/' on click but will instead throw an exception.
Notice here that in the
onPressed: () => Navigator.pushNamed(context, "/"),
we used context passed by to build of MyApp.
The problem is, MyApp is actually a parent of MaterialApp. As it's the widget who instantiate MaterialApp! Therefore MyApp's BuildContext doesn't have a MaterialApp as parent!
To solve this problem, we need to use a different context.
In this situation, the easiest solution is to introduce a new widget as child of MaterialApp. And then use that widget's context to do the Navigator call.
There are a few ways to achieve this. You can extract home into a custom class :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome()
);
}
}
class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
);
}
}
Or you can use Builder :
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Builder(
builder: (context) => Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
),
),
);
}
}
Hy guys, i have the same problem. This is occur for me. The solution what i found is very simple. Only what i did is in a simple code:
void main() {
runApp(MaterialApp(
home: YOURAPP() ,
),
);
}
I hope was useful.
Make sure your current parent widget not with same level with MaterialApp
Wrong Way
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Title'),
),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: RaisedButton(
onPressed: () {
//wrong way: use context in same level tree with MaterialApp
Navigator.push(context,
MaterialPageRoute(builder: (context) => ScanScreen()));
},
child: const Text('SCAN')),
)),
),
);
}
}
Right way
void main() => runApp(MaterialApp(
title: "App",
home: HomeScreen(),
));
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
centerTitle: true,
title: Text('Title'),
),
body: Center(
child: Padding(
padding: EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0),
child: RaisedButton(
onPressed: () {
//right way: use context in below level tree with MaterialApp
Navigator.push(context,
MaterialPageRoute(builder: (context) => ScanScreen()));
},
child: const Text('SCAN')),
)),
);
}
}
Just like with a Scaffold you can use a GlobalKey. It doesn't need context.
final _navKey = GlobalKey<NavigatorState>();
void _navigateToLogin() {
_navKey.currentState.popUntil((r) => r.isFirst);
_navKey.currentState.pushReplacementNamed(LoginRoute.name);
}
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navKey,
...
);
}
I set up this simple example for routing in a flutter app:
import 'package:flutter/material.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
home: new MyHomePage(),
routes: <String, WidgetBuilder>{
'/settings': (BuildContext context) => new SettingsPage(),
},
);
}
}
class MyHomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('TestProject'),
),
body: new Center(
child: new FlatButton(
child: const Text('Go to Settings'),
onPressed: () => Navigator.of(context).pushNamed('/settings')
)
)
);
}
}
class SettingsPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('SettingsPage'),
),
body: new Center(
child: new Text('Settings')
)
);
}
}
Note, that the SettingsPage extends StatelessWidget and not Navigator. I'm not able to reproduce your error.
Does this example help you in building your app? Let me know if I can help you with anything else.
You should rewrite your code in main.dart
FROM:
void main() => runApp(MyApp());
TO
void main() {
runApp(MaterialApp(
title: 'Your title',
home: MyApp(),));}
The point is to have the home property to be your first page
this worked for me, I hope it will help someone in the future
A complete and tested solution:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:my-app/view/main-view.dart';
class SplashView extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: Builder(
builder: (context) => new _SplashContent(),
),
routes: <String, WidgetBuilder>{
'/main': (BuildContext context) => new MainView()}
);
}
}
class _SplashContent extends StatefulWidget{
#override
_SplashContentState createState() => new _SplashContentState();
}
class _SplashContentState extends State<_SplashContent>
with SingleTickerProviderStateMixin {
var _iconAnimationController;
var _iconAnimation;
startTimeout() async {
var duration = const Duration(seconds: 3);
return new Timer(duration, handleTimeout);
}
void handleTimeout() {
Navigator.pushReplacementNamed(context, "/main");
}
#override
void initState() {
super.initState();
_iconAnimationController = new AnimationController(
vsync: this, duration: new Duration(milliseconds: 2000));
_iconAnimation = new CurvedAnimation(
parent: _iconAnimationController, curve: Curves.easeIn);
_iconAnimation.addListener(() => this.setState(() {}));
_iconAnimationController.forward();
startTimeout();
}
#override
Widget build(BuildContext context) {
return new Center(
child: new Image(
image: new AssetImage("images/logo.png"),
width: _iconAnimation.value * 100,
height: _iconAnimation.value * 100,
)
);
}
}
As per this comment If your navigator is inside Material context navigator push will give this error. if you create a new widget and assign it to the material app home navigator will work.
This won't work
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: new Center(child: new Text("Click Me")),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
);
},
),
),
);
}
}
This will work
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new HomeScreen());
}
}
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text("Title"),
),
body: new Center(child: new Text("Click Me")),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
backgroundColor: Colors.orange,
onPressed: () {
print("Clicked");
Navigator.push(
context,
new MaterialPageRoute(builder: (context) => new AddTaskScreen()),
);
},
),
);
}
}
I was facing the same problem and solved by removing home from MaterialApp and use initialRoute instead.
return MaterialApp(
debugShowCheckedModeBanner: false,
initialRoute: '/',
routes: {
'/': (context) => MyApp(),
'/settings': (context) => SettingsPage(),
},
);
And
onTap: () => {
Navigator.pushNamed(context, "/settings")
},
It is Simple
instead using this normal code
`runApp(BasicBankingSystem());`
wrap it with MaterialApp
runApp(MaterialApp(home: BasicBankingSystem()));
It happens because the context on the widget that tries to navigate is still using the material widget.
The short answer for the solution is to :
extract your widget
that has navigation to new class so it has a different context when calling the navigation
When your screen is not navigated from other screen,you don't initially have access to the navigator,Because it is not instantiated yet.So in that case wrap your widget with builder and extract context from there.This worked for me.
builder: (context) => Center(
child: RaisedButton(
child: Text("Foo"),
onPressed: () => Navigator.pushNamed(context, "/"),
),
You ca use this plugin
https://pub.dev/packages/get/versions/2.0.2
in The MaterialApp assign property navigatorKey: Get.key,
MaterialApp(
navigatorKey: Get.key,
initialRoute: "/",
);
you can access Get.toNamed("Your route name");
Change your main function example:
void main() {
runApp(
MaterialApp(
title: 'Your title',
home: MyApp(),
)
);
}
use this
void main() {
runApp(MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),);
}
instead of this
void main() {runApp(MyApp());}
Wrap with materialapp
reproduce code
import 'dart:convert';
import 'package:flutter/material.dart';
void main() {
// reproduce code
runApp(MyApp());
// working switch //
// runApp(
//
// MaterialApp(debugShowCheckedModeBanner: false, home: MyApp()),);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body:
Column(mainAxisAlignment: MainAxisAlignment.center, children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
width: 100,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IntroPage(Isscar4: true)),
);
},
child: RichText(
text: TextSpan(
text: 'CAR',
style: TextStyle(
letterSpacing: 3,
color: Colors.white,
fontWeight: FontWeight.w400),
children: [
TextSpan(
text: '4',
style: TextStyle(
fontSize: 25,
color: Colors.red,
fontWeight: FontWeight.bold))
],
)),
),
),
],
),
SizedBox(
height: 10,
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
height: 100,
width: 100,
child: ElevatedButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => IntroPage(Isscar4: false)),
);
},
child: RichText(
text: TextSpan(
text: 'BIKE',
style: TextStyle(
letterSpacing: 3,
color: Colors.white,
fontWeight: FontWeight.w400),
children: [
TextSpan(
text: '2',
style: TextStyle(
fontSize: 25,
color: Colors.red,
fontWeight: FontWeight.bold))
],
)),
),
),
],
)
])));
}
MaterialApp Swithwidget(istrue) {
return MaterialApp(
home: Scaffold(
body: IntroPage(
Isscar4: istrue,
),
),
);
}
}
class Hi extends StatelessWidget {
const Hi({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Text("df"),
);
}
}
class IntroPage extends StatelessWidget {
final Isscar4;
IntroPage({
Key? key,
required this.Isscar4,
}) : super(key: key);
List<Widget> listPagesViewModel = [];
List<IntroModel> models = [];
#override
Widget build(BuildContext context) {
List<dynamic> intro = fetchIntroApi(Isscar4);
intro.forEach((element) {
var element2 = element as Map<String, dynamic>;
var cd = IntroModel.fromJson(element2);
models.add(cd);
});
models.forEach((element) {
listPagesViewModel.add(Text(""));
});
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Container(),
));
}
List fetchIntroApi(bool bool) {
var four = bool;
if (four) {
var data =
'[ {"name_Title": "title name1","description": "description1"}, {"name_Title": "title name2","description": "description2"}, {"name_Title": "title name3","description": "description3"}, {"name_Title": "title name4","description": "description4"} ]';
return json.decode(data);
} else {
var data =
'[ {"name_Title": "title name","description": "description1"}, {"name_Title": "title name2","description": "description2"}, {"name_Title": "title name3","description": "description3"} ]';
return json.decode(data);
}
}
}
class IntroModel {
String? nameTitle;
String? description;
IntroModel({this.nameTitle, this.description});
IntroModel.fromJson(Map<String, dynamic> json) {
nameTitle = json['name_Title'];
description = json['description'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['name_Title'] = this.nameTitle;
data['description'] = this.description;
return data;
}
}
class Splash extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Splash Screen',
theme: ThemeData(
primarySwatch: Colors.green,
),
home: MyState(),
debugShowCheckedModeBanner: false,
);
}
void main() {
runApp(Splash());
}
class MyState extends StatefulWidget{
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyState> {
#override
void initState() {
super.initState();
Timer(Duration(seconds: 3),
()=>Navigator.pushReplacement(context,
MaterialPageRoute(builder:
(context) =>
Login()
)
)
);
}
#override
Widget build(BuildContext context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center ,
children: [
Container(
child:
Image.asset("assets/images/herosplash.png"),
),
],
),
);
}
}
Builder(
builder: (context) {
return TextButton(
child: const Text('Bearbeiten'),
onPressed:(){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const gotothesiteyouwant()),
);
});
}
),
Here, all you need is to make MaterialApp the parent of your Build. This is because the context that you've used to navigate to a different screen is finding a MaterialApp or a WidgetApp as a parent of the build.
And Since in your case, the situation is the opposite, therefore you need to modify it by either calling a new Stateless widget the parent of is the MaterialApp or by simply using a Builder as home: Builder in MaterialApp.
Hope this would help!
I'm trying to create a Radio in a showDialog, however the animation that occurs on Radio does not appear in showDialog.
For example: when tapped in foo2 nothing happens, and when you exit in showDialog and go back to it, foo2 is selected.
Below is the code and a gif showing what is happening:
import "package:flutter/material.dart";
void main() {
runApp(new ControlleApp());
}
class ControlleApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: "My App",
home: new HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
HomePageState createState() => new HomePageState();
}
enum _RadioGroup {
foo1,
foo2
}
class HomePageState extends State<HomePage> {
_RadioGroup _itemType = _RadioGroup.foo1;
void changeItemType(_RadioGroup type) {
setState(() {
_itemType = type;
});
}
void showDemoDialog<T>({ BuildContext context, Widget child }) {
showDialog<T>(
context: context,
child: child,
);
}
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(backgroundColor: new Color(0xFF26C6DA)),
body: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new InkWell(
onTap: (){
showDemoDialog<String>(
context: context,
child: new SimpleDialog(
title: const Text("show"),
children: <Widget>[
new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Radio<_RadioGroup>(
groupValue: _itemType,
value: _RadioGroup.foo1,
onChanged: changeItemType
),
const Text("foo1"),
new Radio<_RadioGroup>(
groupValue: _itemType,
value: _RadioGroup.foo2,
onChanged: changeItemType
),
const Text("foo2"),
],
)
],
)
);
},
child: new Container(
margin: new EdgeInsets.only(top: 16.0, bottom: 8.0),
child: new Text("Show"),
),
)
],
),
)
);
}
}
Remember that components are immutable.
When you call showDialog, the content of that dialog won't change even if HomePage does.
The solution is easy. You need to refactor a bit your code to something like :
showDialog(
context: context,
builder: (context) => MyForm()
)
and instead of changing the state of HomePage, you instead change the state of MyForm.
example :
class Test extends StatelessWidget {
void onSubmit(String result) {
print(result);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(
onPressed: () => showDialog(context: context, builder: (context) => MyForm(onSubmit: onSubmit)),
child: Text("dialog"),
),
),
);
}
}
typedef void MyFormCallback(String result);
class MyForm extends StatefulWidget {
final MyFormCallback onSubmit;
MyForm({this.onSubmit});
#override
_MyFormState createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
String value = "foo";
#override
Widget build(BuildContext context) {
return SimpleDialog(
title: Text("My form"),
children: <Widget>[
Radio(
groupValue: value,
onChanged: (value) => setState(() => this.value = value),
value: "foo",
),
Radio(
groupValue: value,
onChanged: (value) => setState(() => this.value = value),
value: "bar",
),
FlatButton(
onPressed: () {
Navigator.pop(context);
widget.onSubmit(value);
},
child: new Text("submit"),
)
],
);
}
}