Flutter image builder callback issue - flutter

I have a problem with using the Image error builder. For example I want to change another widget in the tree to not have a colour. I thought about using a boolean flag but it seems messy. Is there a simple way to do this. Below is an example of what i mean
return Stack(
children: [
Image.file(
File("Some path"),
errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
// If error builder draws i want the container below colour to become transparent...
// how do i do this?
return Text('Error');
},
),
Container(
height: 100,
width: 200,
color: Colors.red,
),
],
);

You can copy paste run full code below
You can use StreamBuilder and call _events.add(Colors.transparent); in errorBuilder
In working demo, I use image.network to simulate your case, you can directly modify to Image.file
code snippet
Image.file(
File(widget.path),
errorBuilder:
(BuildContext context, Object exception, StackTrace stackTrace) {
_events.add(Colors.transparent);
return Text('Error');
},
),
StreamBuilder<Color>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<Color> snapshot) {
return Container(
height: 100,
width: 200,
color: snapshot.data,
);
})
working demo
full code
import 'package:flutter/material.dart';
import 'dart:async';
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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
ImageHandelError(
path: 'https://picsum.photos/250?image=9',
),
ImageHandelError(
path: 'not exist',
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class ImageHandelError extends StatefulWidget {
String path;
ImageHandelError({this.path});
#override
_ImageHandelErrorState createState() => _ImageHandelErrorState();
}
class _ImageHandelErrorState extends State<ImageHandelError> {
StreamController<Color> _events;
#override
initState() {
super.initState();
_events = StreamController<Color>();
_events.add(Colors.red);
}
#override
Widget build(BuildContext context) {
return Stack(
children: [
Image.network(
widget.path,
errorBuilder:
(BuildContext context, Object exception, StackTrace stackTrace) {
_events.add(Colors.transparent);
return Text('Error');
},
),
StreamBuilder<Color>(
stream: _events.stream,
builder: (BuildContext context, AsyncSnapshot<Color> snapshot) {
return Container(
height: 100,
width: 200,
color: snapshot.data,
);
})
],
);
}
}

I would advice you to use an empty image to fill the gap when ever error happen
child: FadeInImage.assetNetwork(
image: "https://cdn-icons-png.flaticon.com/512/270/270014.png",
fit: BoxFit.fitWidth,
placeholder: Assets.logo_place_holder,//this the image you have prepared
imageErrorBuilder: (_, __, ___) {
return Image.asset(Assets.logo_place_holder); //this is what will fill the Container in case error happened
},
),
or you can Just return a Container and assign color to it and it will spared to fill out the area
this is what it will look like
and this what its look like with Container
this is what it will look like
return Container(color: Colors.red,);

Related

How can I show overlay on top of the whole app in flutter?

I want to show an overlay on the whole app so I tried to insert an overlay entry on the context of MaterialApp (root widget) but the problem is I'm getting the null value on invoking the following method :
Overlay.of(context);
GetMaterialApp.router(
debugShowCheckedModeBanner: false,
theme: AppTheme.lightTheme,
scaffoldMessengerKey: Keys.scaffold,
scrollBehavior: MyCustomScrollBehavior(),
routeInformationParser: WebRoutes.goRouter.routeInformationParser,
routerDelegate: WebRoutes.goRouter.routerDelegate,
routeInformationProvider: WebRoutes.goRouter.routeInformationProvider,
builder: (context, child) {
WidgetsBinding.instance.addPostFrameCallback((_){
addOverlay(context);
});
return child;
}
void addOverlay(BuildContext context) {
print(Overlay.of(context));
return Overlay.of(context)?.insert(OverlayEntry(
builder: (context) {
return SomeWidget();
},
));
}
Is there any way to get the state of overlay using the context of this root widget as I want to show the overlay globally.
Thanks alot, I really appreciate that If someone helps me.
MaterialApp(
navigatorKey: getIt.get<NavigatorService>().navigatorKey,
theme: AppTheme.defaultTheme,
initialRoute: AppRoutes.splashScreen,
builder: (context, child) {
return Scaffold(
body: Stack(
children: [
child!,
Positioned(
top: 15,
child: Container(
color: Colors.red,
height: 50,
width: MediaQuery.of(context).size.width,
child: const Center(child: Text("HI I AM AN OVERLAY")),
),
),
],
),
);
},
onGenerateRoute: AppRoutes.onGenerateRoute,
),
You can achieve that by create a class responsible to display/remove the overlay, this class need receive a BuildContext when creating to be able to create an instance of Overlay.
Basically what you need to do are:
Create a class OverlayScreen that build the OverlayState && OverlayEntry (in this case the OverylayEntry will be a list of OverlayEntry since we might have more than one Overlay on the screen so we can remove all of them at once).
Create an instance of this class earlier in your app (e.g MyApp). In your case you'll need to call this inside Material.router...builder param.
Access this overlayScreen in your HomePage to display|removeAll overlays
Lets create our OverlayScreen
import 'package:flutter/material.dart';
class OverlayScreen {
/// Create an Overlay on the screen
/// Declared [overlayEntrys] as List<OverlayEntry> because we might have
/// more than one Overlay on the screen, so we keep it on a list and remove all at once
BuildContext _context;
OverlayState? overlayState;
List<OverlayEntry>? overlayEntrys;
void closeAll() {
for (final overlay in overlayEntrys ?? <OverlayEntry>[]) {
overlay.remove();
}
overlayEntrys?.clear();
}
void show() {
overlayEntrys?.add(
OverlayEntry(
builder: (context) {
return _buildOverlayWidget();
},
),
);
overlayState?.insert(overlayEntrys!.last);
}
OverlayScreen._create(this._context) {
overlayState = Overlay.of(_context);
overlayEntrys = [];
}
factory OverlayScreen.of(BuildContext context) {
return OverlayScreen._create(context);
}
Widget _buildOverlayWidget() {
return Positioned(
top: 20,
left: 20,
right: 20,
child: Container(
width: 300,
color: Colors.black,
height: 300,
child: const Text("MY CHAT"),
),
);
}
}
Now lets create an instance on MyApp
// Need to have it global to be able to access everywhere
OverlayScreen? overlayScreen;
void main() {
runApp(
const MyApp(),
);
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
home: const HomePage(),
builder: (context, child) {
return Overlay(
initialEntries: [
OverlayEntry(
builder: (context) {
// Create an instance of `OverlayScreen` to be accessed globally
overlayScreen = OverlayScreen.of(context);
return child ?? const SizedBox();
},
),
],
);
},
);
}
}
To finalise lets create our HomePage and access our overlayScreen there there
import 'package:flutter/material.dart';
import 'package:overlay_all_app/src/overlay_screen.dart';
class HomePage extends StatelessWidget {
const HomePage({super.key});
#override
Widget build(BuildContext context) {
// Create an instance of OverlayScreen
final overlayScreen = OverlayScreen.of(context);
return Scaffold(
appBar: AppBar(
title: const Text('Home'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextButton(
onPressed: () {
// display the overlay
overlayScreen.show();
},
child: const Text('Display Overlay'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// Call your next screen here
},
child: const Text('Go to next page'),
),
const SizedBox(height: 30),
TextButton(
onPressed: () {
// removed all overlays on the screen
overlayScreen.closeAll();
},
child: const Text('Close Overlay'),
),
],
),
),
);
}
}
That's it. You can use this class OverlayScreen to show/removeAll wherever you want.
I created a PR with sample code, check it out https://github.com/antonio-nicolau/flutter-working-with-overlay
import 'package:flutter/material.dart';
import 'package:get/get_navigation/src/root/get_material_app.dart';
import 'package:go_router/go_router.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(App2());
}
class App2 extends StatelessWidget {
App2({super.key});
final _router = GoRouter(
routes: [
GoRoute(
path: '/',
builder: (context, state) => const OverlayWrapper(),
),
],
);
#override
Widget build(BuildContext context) {
return GetMaterialApp.router(
routeInformationParser: _router.routeInformationParser,
routerDelegate: _router.routerDelegate,
routeInformationProvider: _router.routeInformationProvider,
);
}
}
class OverlayWrapper extends StatefulWidget {
const OverlayWrapper({Key? key}) : super(key: key);
#override
State<OverlayWrapper> createState() => _OverlayWrapperState();
}
class _OverlayWrapperState extends State<OverlayWrapper> {
#override
void initState() {
super.initState();
}
showOverLay() {
OverlayEntry overlayEntry = OverlayEntry(
builder: (context) => Container(
color: Colors.red,
child: const Text('data'),
),
);
Overlay.of(context).insert(overlayEntry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () {
showOverLay();
},
child: const Text(
'ShowOverlay',
style: TextStyle(),
),
),
),
);
}
}

How to implement telegram style pop up in flutter? [duplicate]

I want a widget that will sit on top of the entire application. When I have tried to do this with Overlay.of(context).insert the overlay would later disappear after replacing that route. Is there a way I can have a widget on top of my app even if the screen is later popped?
Maybe a more optimal way exists, but as an option this is an example with two pages, local navigator and Overlay.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: WillPopScope(
onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
child: LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
return Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/page2':
return MaterialPageRoute(builder: (_) => Page2());
default:
return MaterialPageRoute(builder: (_) => Page1(_navigatorKey));
}
},
);
},
),
),
);
}
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
width: 56,
height: 56,
top: size.height - 72,
left: size.width - 72,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () => print('ON TAP OVERLAY!'),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
),
),
),
);
}),
);
}
}
class Page1 extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
Page1(this.navigatorKey);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[200],
appBar: AppBar(title: Text('Page1')),
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('go to Page2'),
onPressed: () => navigatorKey.currentState.pushNamed('/page2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow[200],
appBar: AppBar(title: Text('back to Page1')),
body: Container(
alignment: Alignment.center,
child: Text('Page 2'),
),
);
}
}
Screenshot (Null safe):
Full code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Offset _offset = Offset.zero;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
builder: (context, child) {
return Stack(
children: [
child!,
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onPanUpdate: (d) => setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.black,
child: Icon(Icons.add),
),
),
),
],
);
},
);
}
}
LoginPage:
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage())),
child: Text('Page2'),
),
),
);
}
}
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: FlutterLogo(size: 300),
);
}
}
After reading the comments, find github-repo-link
created an overlay that will sit on top of everything
that can be called from anywhere.
just 4 easy steps to follow
flutterflutter-layout
STEP-1: in main.dart:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Stack( <-- using stack
children: [
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
OverlayView(),<-- my overlay widget
],
),
);
}
}
STEP-2: OverLayView.dart
class OverlayView extends StatelessWidget {
const OverlayView({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( <--- IMP , using ValueListenableBuilder for showing/removing overlay
valueListenable: Loader.appLoader.loaderShowingNotifier,
builder: (context, value, child) {
if (value) {
return yourOverLayWidget(); <-- your awesome overlay
} else {
return Container();
}
},
);
}
STEP-3: loder_controller.dart (to show/hide)
class Loader {
static final Loader appLoader = Loader(); <-- singleton
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void showLoader() { <-- using to show from anywhere
loaderShowingNotifier.value = true;
}
void hideLoader() { <-- using to hide from anywhere
loaderShowingNotifier.value = false;
}
void setText({String errorMessage}) { <-- using to change error message from anywhere
loaderTextNotifier.value = errorMessage;
}
void setImage() { <-- DIY
// same as that of setText //
}
}
FINAL STEP-4: show/hide loder
I'm showing it, on boilerplate code of increment method to show the loader
void _incrementCounter() async {
Loader.appLoader.showLoader(); <-- show loder
Loader.appLoader.setText(errorMessage: 'this is custom error message');<-- set custom message
await Future.delayed(Duration(seconds: 5)); <-- im hiding it after 5 sec
Loader.appLoader.hideLoader(); <-- do whatever you want
}
As a supplement to other answers: If you want to show some overlays, the flutter_portal may indeed be a better choice that is simpler to use.
Basically, it looks like:
PortalTarget(
// Declarative
portalFollower: MyAwesomeOverlayWidget(),
// Align anywhere you like. Now `portalFollower` floats at right of `child`
anchor: const Aligned(follower: Alignment.topLeft, target: Alignment.topRight),
child: MyChildWidget(),
)
Notice that it is declarative (not imperative as opposed to Overlay). Moreover, you get the bonus that the alignment is very easy, and the context is intuitive.
Disclaimer: I am the current owner of this library.
Have you tried to add a Navigator as a child/descendant of your Scaffold? As far as I remember, the default navigator is in the MaterialApp, which is above everything. When you add your own Navigator, your routing will happen under the Scaffold rather than above it in the tree.

Issue with StreamBuilder and streams in Flutter (receiving duplicated data)

today I have faced a problem with streams and StreamBuilder.
The problem is the following:
If you have multiple StreamBuilder widgets listening to the same stream, and you add data into its sink, this data will go out through the stream the amount of StreamBuilder listeners that you have, in other words:
If you have one StreamController (or BehaviorSubject) , k amount of widgets of type StreamBuilder, and you try to do StreamController.sink.add(event), this event will go out k times trough the stream, one per StreamBuilder.
Is that an expected behavior (expected behavoir = input an event and listening just once from the other side independentlly of the amount of listeners) ? I was able to "fix" this encapsulating almost all the widget tree into one StreamBuilder, but this isnt as optimal as the first approach because of you are rendering the whole tree instead of some little node widgets.
Here I left some code to test it if you want (This code is a modification of flutter create project_name project).
Thank you!
(P.D: This works well if you just listen the streams without StreamBuilder, i.e: streamController.stream.listen..)
import 'dart:async';
import 'package:flutter/cupertino.dart';
import 'package:rxdart/subjects.dart';
class MyAppBloc with ChangeNotifier {
int _currentIndex;
BehaviorSubject<bool> _controller;
MyAppBloc() {
_currentIndex = 0;
_controller = BehaviorSubject<bool>();
}
Stream<int> get currentIndex => _controller.stream.map<int>((event) {
print('[event: $event]');
_currentIndex++;
return _currentIndex;
});
StreamSink<bool> get increment => _controller.sink;
void close() {
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:test_project/bloc/my_app_bloc.dart';
class MyHomePage extends StatelessWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
Widget leadingBuilder(MyAppBloc bloc) {
return StreamBuilder<int>(
initialData: 0,
stream: bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print('[leadingBuilderSnapshot: $snapshot]');
return Text(snapshot.data.toString());
},
);
}
StreamBuilder<int> counterBuilder(MyAppBloc bloc) {
return StreamBuilder<int>(
initialData: 0,
stream: bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
print('[counterBuilderSnapshot: $snapshot]');
return Text(
snapshot.data.toString(),
style: Theme.of(context).textTheme.headline4,
);
},
);
}
#override
Widget build(BuildContext context) {
print('[build]');
final _bloc = Provider.of<MyAppBloc>(context);
return Scaffold(
appBar: AppBar(
leading: Container(
width: 30,
height: 30,
alignment: Alignment.center,
child: leadingBuilder(_bloc),
),
title: Text(title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
StreamBuilder<int>(
initialData: 0,
stream: _bloc.currentIndex,
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
return Text('${snapshot.data}');
},
),
Text(
'You have pushed the button this many times:',
),
counterBuilder(_bloc),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => _bloc.increment.add(true),
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Because currentIndex is a getter and you are using map(), a new stream will be created every time
you call bloc.currentIndex and StreamBuilder will listen to it.
So in original code , there are actually 1 StreamControlller, and k Streams. (k: number of StreamBuilder)
To solve your problem, you can create an eventController, and listen to it inside a bloc to execute your logic. (eventStream is listened from only bloc itself, it will be created just once)
for example:
class MyAppBloc {
MyAppBloc() {
_eventController.listen((event) {
print('[event: $event]');
_indexController.add(currentIndex.value + 1);
});
}
final _indexController = BehaviorSubject<int>.seeded(0);
final _eventController = PublishSubject<bool>();
ValueStream<int> get currentIndex => _indexController.stream;
StreamSink<bool> get increment => _eventController.sink;
void close() {
_indexController?.close();
_eventController?.close();
}
}
You have to use 2 streams/sink and put the increment outside the get stream.
import 'dart:async';
import 'package:rxdart/subjects.dart';
class Bloc {
int _counter = 0;
Bloc() {
_controller.stream.listen(_incrementStream);
}
final _counterStream = BehaviorSubject<int>.seeded(0);
Stream get presentCounter => _counterStream.stream;
Sink get _addValue => _counterStream.sink;
StreamController _controller = BehaviorSubject<bool>();
StreamSink<bool> get incrementCounter => _controller.sink;
void _incrementStream(data) {
_counter += 1;
_addValue.add(_counter);
}
void dispose() {
_counterStream.close();
_controller.close();
}
}
import 'package:flutter/material.dart';
import 'package:increment/bloc.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#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> {
Bloc _bloc = Bloc();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
StreamBuilder<int>(
stream: _bloc.presentCounter,
builder: (context, snapshot) {
return Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
);
}),
SizedBox(
height: 60,
),
StreamBuilder<int>(
stream: _bloc.presentCounter,
builder: (context, snapshot) {
return Text(
'${snapshot.data}',
style: Theme.of(context).textTheme.headline4,
);
}),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {
_bloc.incrementCounter.add(true);
},
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
}
}

How to zoom image in flutter with CachedNetworkImage widget

can you suggest a way to zoom an image inside a CachedNetworkImage?
Here is my code
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
),
I tried to wrap CachedNetworkImage in a photo_view widget but it does not work
#override
Widget build(BuildContext context) {
return Container(
child: PhotoView(
imageProvider: CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
)
)
);
}
You can copy paste run full code below
Package Cached network image provide CachedNetworkImageProvider
code snippet
PhotoView(
imageProvider:
CachedNetworkImageProvider("http://via.placeholder.com/350x150"),
)
working demo
full code
import 'package:flutter/material.dart';
import 'package:photo_view/photo_view.dart';
import 'package:cached_network_image/cached_network_image.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
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> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(flex: 1, child: PhotoViewTest()),
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
class PhotoViewTest extends StatefulWidget {
#override
_PhotoViewTestState createState() => _PhotoViewTestState();
}
class _PhotoViewTestState extends State<PhotoViewTest> {
#override
Widget build(BuildContext context) {
return Container(
child: PhotoView(
imageProvider:
CachedNetworkImageProvider("http://via.placeholder.com/350x150"),
),
);
}
}
You can wrap Photo view inside Cached Network image like this code, so you can use advantages of both cached network image and photo view
CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
imageBuilder: (context, imageProvider) => PhotoView(
imageProvider: imageProvider,
),
placeholder: (context, url) =>
CircularProgressIndicator(),
errorWidget: (context, url, error) =>
Icon(Icons.error),
)
Widget build(BuildContext context) {
return Container(
child: CachedNetworkImage(
imageUrl: "http://via.placeholder.com/350x150",
imageBuilder: (context, imageProvider) => PhotoView(
imageProvider: imageProvider,
)
),
);
}

How to overlay a widget on top of a flutter App?

I want a widget that will sit on top of the entire application. When I have tried to do this with Overlay.of(context).insert the overlay would later disappear after replacing that route. Is there a way I can have a widget on top of my app even if the screen is later popped?
Maybe a more optimal way exists, but as an option this is an example with two pages, local navigator and Overlay.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: WillPopScope(
onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
child: LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
return Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/page2':
return MaterialPageRoute(builder: (_) => Page2());
default:
return MaterialPageRoute(builder: (_) => Page1(_navigatorKey));
}
},
);
},
),
),
);
}
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
width: 56,
height: 56,
top: size.height - 72,
left: size.width - 72,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () => print('ON TAP OVERLAY!'),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
),
),
),
);
}),
);
}
}
class Page1 extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
Page1(this.navigatorKey);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[200],
appBar: AppBar(title: Text('Page1')),
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('go to Page2'),
onPressed: () => navigatorKey.currentState.pushNamed('/page2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow[200],
appBar: AppBar(title: Text('back to Page1')),
body: Container(
alignment: Alignment.center,
child: Text('Page 2'),
),
);
}
}
Screenshot (Null safe):
Full code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Offset _offset = Offset.zero;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
builder: (context, child) {
return Stack(
children: [
child!,
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onPanUpdate: (d) => setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.black,
child: Icon(Icons.add),
),
),
),
],
);
},
);
}
}
LoginPage:
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage())),
child: Text('Page2'),
),
),
);
}
}
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: FlutterLogo(size: 300),
);
}
}
After reading the comments, find github-repo-link
created an overlay that will sit on top of everything
that can be called from anywhere.
just 4 easy steps to follow
flutterflutter-layout
STEP-1: in main.dart:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Stack( <-- using stack
children: [
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
OverlayView(),<-- my overlay widget
],
),
);
}
}
STEP-2: OverLayView.dart
class OverlayView extends StatelessWidget {
const OverlayView({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( <--- IMP , using ValueListenableBuilder for showing/removing overlay
valueListenable: Loader.appLoader.loaderShowingNotifier,
builder: (context, value, child) {
if (value) {
return yourOverLayWidget(); <-- your awesome overlay
} else {
return Container();
}
},
);
}
STEP-3: loder_controller.dart (to show/hide)
class Loader {
static final Loader appLoader = Loader(); <-- singleton
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void showLoader() { <-- using to show from anywhere
loaderShowingNotifier.value = true;
}
void hideLoader() { <-- using to hide from anywhere
loaderShowingNotifier.value = false;
}
void setText({String errorMessage}) { <-- using to change error message from anywhere
loaderTextNotifier.value = errorMessage;
}
void setImage() { <-- DIY
// same as that of setText //
}
}
FINAL STEP-4: show/hide loder
I'm showing it, on boilerplate code of increment method to show the loader
void _incrementCounter() async {
Loader.appLoader.showLoader(); <-- show loder
Loader.appLoader.setText(errorMessage: 'this is custom error message');<-- set custom message
await Future.delayed(Duration(seconds: 5)); <-- im hiding it after 5 sec
Loader.appLoader.hideLoader(); <-- do whatever you want
}
As a supplement to other answers: If you want to show some overlays, the flutter_portal may indeed be a better choice that is simpler to use.
Basically, it looks like:
PortalTarget(
// Declarative
portalFollower: MyAwesomeOverlayWidget(),
// Align anywhere you like. Now `portalFollower` floats at right of `child`
anchor: const Aligned(follower: Alignment.topLeft, target: Alignment.topRight),
child: MyChildWidget(),
)
Notice that it is declarative (not imperative as opposed to Overlay). Moreover, you get the bonus that the alignment is very easy, and the context is intuitive.
Disclaimer: I am the current owner of this library.
Have you tried to add a Navigator as a child/descendant of your Scaffold? As far as I remember, the default navigator is in the MaterialApp, which is above everything. When you add your own Navigator, your routing will happen under the Scaffold rather than above it in the tree.