Navigate back from FlutterDocumentPicker - flutter

I try to navigate back from `FlutterDocumentPicker, when a file was not selected, but it shows black screen and there is no error in console. How to fix it?
Future<void> _getFile(BuildContext context) async {
final FlutterDocumentPickerParams params = FlutterDocumentPickerParams(
allowedFileExtensions: ['txt'],
);
final String path = await FlutterDocumentPicker.openDocument(params: params)
.catchError((dynamic e) {
print(e.toString());
return; // ?
});
print(path); // null, i.e. file was not selected
if (path == null) {
// back button pressed i.e. file was not selected
Navigator.pop(
context); // can not find previous screen, it shows black screen, there is no error in console
return; // ?
}
...

The black screen means, your root/main widget has been popped out of the Navigation Stack. Meaning the widget you are trying to pop is not the FlutterDocumentPicker but the root widget.
I don't think you need to pop the current widget since the output path is available only after the FlutterDocumentPicker widget is disposed/closed.
Just return from the method, no need to pop the widget.

Related

Changing method behavior depending source screen

I have a widget with this method in flutter that is called by two different screens, I would like 'Navigator.pop' to change its behavior depending on which screen calls it.
On the first screen it would apply a common 'pop', and on the second screen, for a specific route. Can you help me with this?
`
void salvarCartao(InputCartaoDto cartao, BuildContext context) async {
var cartaoDto = await AdicionarCartaoCommand().execute(cartao, context);
if (cartaoDto != null) {
var usuarioCorrente = await ObterUsuarioCorrenteCommand().execute();
var listaCartoes = usuarioCorrente?.cartoes;
listaCartoes?.add(cartaoDto);
AtualizarUsuarioCommand().execute(usuarioCorrente!);
}
//if screen 1 called the method:
Navigator.pop(context);
//if screen 2:
Navigator.popUntil(context, ModalRoute.withName('/carrinho-pagamento'));
}
`
I'm actually still learning flutter, I couldn't think of a solution with my current knowledge
then redefine your function. Ex:
void salvarCartao(InputCartaoDto cartao, BuildContext context, int opt) async {
var cartaoDto = await AdicionarCartaoCommand().execute(cartao, context);
if (cartaoDto != null) {
var usuarioCorrente = await ObterUsuarioCorrenteCommand().execute();
var listaCartoes = usuarioCorrente?.cartoes;
listaCartoes?.add(cartaoDto);
AtualizarUsuarioCommand().execute(usuarioCorrente!);
}
//if screen 1 called the method:
if(opt ==1)
Navigator.pop(context);
else
//if screen 2:
Navigator.popUntil(context, ModalRoute.withName('/carrinho-pagamento'));
}
You can pass a flag to the salvarCartao function, depending on which screen calls it.
isFromScreen2 ? Navigator.popUntil(context, ModalRoute.withName('/carrinho-pagamento')) : Navigator.pop(context);
or
if (isFromScreen2) {
Navigator.popUntil(context, ModalRoute.withName('/carrinho-pagamento'))
} else {
Navigator.pop(context);
}

How update data from different widgets Flutter

I got a list of PostItem with a FutureBuilder.
PostItem got a like button and a like count.
When a click on the post, I go into its details. And I can like the post on this screen.
Here is when I click on the like button in detail screen :
Future<void> _updateLike() async
{
PhpPost phpPost = PhpPost();
phpPost.posteModel = widget.postModel;
if(_isLike)
{
String res = await phpPost.unlikePost();
if(res=="OK")
{
setState(() {
_isLike = false;
});
}
}
else
{
String res = await phpPost.likePost();
if(res=="OK")
{
setState(() {
_isLike = true;
});
}
}
widget.postModel.isLike = _isLike;
}
The screen detail update nicely but when I go back at the home screen the post item not updated the like.
Here is how I go to detail from post item :
Navigator.pushNamed(context, '/post_detail', arguments: widget.postModel);
setState here is a local for only this widget and wont rebuild the home page
a simple solution is to try call setState after the await Navigator.pushNamed
which will call setState for the home page after we close the post page
await Navigator.pushNamed(context, '/post_detail', arguments: widget.postModel);
setState((){});
this will work if you are calculate the like count at the build method or you should re-calculate it inside setState
a better solution is to not use a setState at all for handling a user-data change
and use state management solution like provider with ChangeNotifier, bloc or riverpod
which you will have a controller that will change the data and update the widget

Navigator 2.0 - WillPopScope vs BackButtonListener

I have an app with a BottomNavigationBar and an IndexedStack which shows the tab content. Each tab has its own Router with its own RouterDelegate to mimic iOS-style tab behavior (where each tab has its own navigation controller).
Before, this app was only published on iOS. I'm now working on the Android version and need to correctly support the Android hardware back button. I did this by implementing a ChildBackButtonDispatchers per tab, which are a child of the parent RootBackButtonDispatcher. This works.
The issue I'm having now is that I use WillPopScope widgets to save a user's input when they leave a screen. This works correctly if the user taps the back button in the AppBar, but the callback isn't triggered when the user taps the hardware back button. I implemented BackButtonListeners on these screens as well, but this means I have to wrap the screens in both WillPopScopes and BackButtonListeners, both calling the same callback.
It this how it's supposed to be, or am I doing something wrong?
Relevant widget hierarchy:
MaterialApp
Navigator
tab interface with IndexedStack
the selected tab Widget the tab's Router
Navigator
multiple pages, with on the last page in the stack...
BackButtonListener
WillPopScope
Scaffold
My (simplified) router delegate looks like this:
class AppRouterDelegate extends RouterDelegate<AppRoute>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<AppRoute> {
AppRouterDelegate({
List<MaterialPage> initialPages = const [],
}) : _pages = initialPages;
final navigatorKey = GlobalKey<NavigatorState>();
final List<MaterialPage> _pages;
List<MaterialPage> get pages => List.unmodifiable(_pages);
void push(AppRoute route) {
final shouldAddPage = _pages.isEmpty || (_pages.last.arguments as AppRoute != route);
if (!shouldAddPage) {
return;
}
_pages.add(route.page);
notifyListeners();
}
#override
Future<void> setNewRoutePath(AppRoute route) async {
_pages.clear();
_pages.add(route.page);
notifyListeners();
return SynchronousFuture(null);
}
#override
Future<bool> popRoute() {
if (canPop) {
pop();
return SynchronousFuture(true);
}
return SynchronousFuture(false);
}
bool get canPop => _pages.length > 1;
void pop() {
if (canPop) {
_pages.remove(_pages.last);
notifyListeners();
}
}
void popTillRoot() {
while (canPop) {
_pages.remove(_pages.last);
}
notifyListeners();
}
bool _onPopPage(Route<dynamic> route, result) {
final didPop = route.didPop(result);
if (!didPop) {
return false;
}
if (canPop) {
pop();
return true;
} else {
return false;
}
}
#override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
onPopPage: _onPopPage,
pages: pages,
);
}
}
I found this Flutter issue which makes me think I shouldn't have the WillPopScope at all, but without it the taps in the AppBar are not caught...
I know this question is old, but here's an answer for others who arrive here.
From the AppBar leading documentation (emphasis mine):
If this is null and automaticallyImplyLeading is set to true, the AppBar will imply an appropriate widget. For example, if the AppBar is in a Scaffold that also has a Drawer, the Scaffold will fill this widget with an IconButton that opens the drawer (using Icons.menu). If there's no Drawer and the parent Navigator can go back, the AppBar will use a BackButton that calls Navigator.maybePop.
So in order to make the Android back button work the same way as the App Bar's back button, you need to use the Navigator.maybePop method, which will respect WillPopScope.
Conveniently, Flutter provides PopNavigatorRouterDelegateMixin to make this easy; it provides an implementation of popRoute that uses maybePop and therefore will work identically to the App Bar's automatically-generated back/dismiss button. The nice thing about Flutter being open source is that you can jump into the Flutter code to verify what the mixin is doing:
mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> {
/// The key used for retrieving the current navigator.
///
/// When using this mixin, be sure to use this key to create the navigator.
GlobalKey<NavigatorState>? get navigatorKey;
#override
Future<bool> popRoute() {
final NavigatorState? navigator = navigatorKey?.currentState;
if (navigator == null)
return SynchronousFuture<bool>(false);
return navigator.maybePop();
}
}
So I think the only mistake in your code is that, even though you've mixed-in PopNavigatorRouterDelegateMixin on your router delegate, you are also providing your own override of popRoute. When the user taps the Android back button, your popRoute implementation is called, and it just pops the last page. If you delete your popRoute override and let the mixin do its thing, then the Android back button will function identically to the App Bar back/dismiss button.

Load a Route before pushing it via Navigator in flutter?

I'm currently trying to push a named route in flutter.
Works good so far, but the Asset Image for the background is loaded after the route was pushed via Navigator, which does not look good.
Currently I push the route like this:
#override
void initState() {
super.initState();
Timer(Duration(seconds: 5), () =>
Navigator.pushNamed(context, routeToPage2)
);
}
Is there any way to load a Page / Route without pushing it in the first place, so everything is build correctly when the route is pushed after the set time?
I guess the Asset Image gets loaded on routeToPage2? In this case the push happens everytime before the image gets load.
you can use a routegenerator for your page route and popUntil. This behavior will remove existing pages off the stack and push a single page called home page on. The MaterialPageRoute builds your route that is pushed to the navigator. Load your asset in the scaffold of HomePage. It should not require a delay to render
Navigator.of(context)
.popUntil(ModalRoute.withName(RouteGenerator.homePage));
class RouteGenerator {
static const String homePage = "/home";
static const String customPage = "/custom";
RouteGenerator._();
static Route<dynamic> handleRoute(RouteSettings routeSettings) {
Widget childWidget;
switch (routeSettings.name) {
case homePage:
{
childWidget = HomePageWidget(title: 'Home Page');
}
break;
case customPage:
{
final args = routeSettings.arguments as CustomView;
childWidget = CustomPageWidget(args);
}
break;
default:
throw FormatException("Route Not Found");
}
return MaterialPageRoute(builder: (context) => childWidget);
}
}
Sorry for being a bit too vague, maybe I should have explained my problem more detailed.
So basically I have Page1, which pushes after a Timer finished to Page2.
On Page2, I have an Image-Asset, which loads shortly after Page2 is displayed, which did not look nice. This asset is saved on the device.
I wanted to load Page2 somehow in the background, while Page1 is still being displayed to the user, so the Background-image of Page2 does not pop up after the Page is shown.
But I have found myself a suitable solution on my problem.
I use the following code on Page1:
late Image backgroundImage;
#override
void initState() {
super.initState();
backgroundImage = Image.asset("path to image");
Timer(Duration(seconds: 5),
() => Navigator.pushReplacementNamed(context, page2));
}
#override
void didChangeDependencies(){
super.didChangeDependencies();
precacheImage(backgroundImage.image, context);
}
This results in preloading the image while Page1 is shown, so the Background-Image does not pop up after Page2 is displayed to the user.

How to avoid duplicate screen on top in Flutter

Giving that I have declared my routes in MaterialApp of my flutter application, now I am using
Navigator.pushNamed(context,ScreenA);
now on some user event I need to open ScreenA but only if ScreenA is not there already otherwise just update arguments in that ScreenA.
Have a look at this. You can await a result from all the pages you open from Screen A and use the values returned from these pages in Screen A once you pop back to it
You can check the current top screen and set your condition like below,
final newRouteName = "/NewRoute"; // Here add your route name
bool isNewRouteSameAsCurrent = false;
Navigator.popUntil(context, (route) {
if (route.settings.name == newRouteName) {
isNewRouteSameAsCurrent = true;
}
return true;
});
if (!isNewRouteSameAsCurrent) {
Navigator.pushNamed(context, newRouteName);
}
Refer.