Bottom sheet behind navigation bar - android

I have a screen with a bottom navigation bar and a floating action button.
I'm trying to display a bottom sheet that comes up from behind the bottom navigation bar. Also, I would like to see the barrier. Only the bottom navigation bar and the bottom sheet should be clearly visible, while the rest of the screen must be darkened. (kind of this).
For navigation, I'm using GetX, so first I tried to specify useRootNavigator: true to Get.bottomSheet, but didn't work.
I've seen many answers which suggest the use of showModalBottomSheet with useRootNavigator: true. Again, didn't work.
I've already achieved this with a custom solution (posted here), but I want to understand what can inhibit the effect of useRootNavigator or if there is another built-in solution to achieve my goal.
LE:
I'm doing simple calls, nothing fancy.
References to the documentation below.
GetX package: https://pub.dev/packages/get
showModalBottomSheet
Source code example:
https://gist.github.com/alexgrusu/8fd173e56d4046cdbda487e4b98bd950

The solution for this behavior can be achieved through nested navigation.
First, define the keys of the navigators.
class NavigatorKeys {
static final GlobalKey<NavigatorState> mainNavigatorKey = GlobalKey();
static final GlobalKey<NavigatorState> secondaryNavigatorKey =
GlobalKey();
}
The main key can be assigned to the navigatorKey of the MaterialApp.
Next, let's consider that our app's home is the dashboard screen so that our MaterialApp would look like so.
MaterialApp(
title: 'Demo App',
home: const DashboardScreen(),
navigatorKey: NavigatorKeys.mainNavigatorKey,
);
Then, the build method for the DashboardScreen can be implemented as shown below.
#override
Widget build(BuildContext context) {
return Scaffold(
body: Navigator(
key: NavigatorKeys.secondaryNavigatorKey,
onGenerateRoute: (routeSettings) {
return MaterialPageRoute(
builder: (context) =>
_buildMainUi(),
);
},
),
);
},
),
bottomNavigationBar: _buildNavigationBar(),
);
}
_buildMainUi() => Scaffold(body: Center(child: Text('Dashboard')));
In the end, simply pass the context of the secondary navigator to the showModalBottomSheet method and set useRootNavigator to false.
showModalBottomSheet<Widget?>(
useRootNavigator: false,
context: NavigatorKeys.secondaryNavigatorKey.currentContext!,
builder: (BuildContext context) {
return Container(height: 200, child: Text('hello'));
},
);

Related

How to access and change a variable from a different file in flutter

I am trying to access and change a variable from a differant file in flutter.
basically, I have created a file called ```colors.dart``` where I'll save the data on the colors that will be used in flutter app.
import 'package:flutter/material.dart';
class colors {
static Color bgColor = Color(0xfff8f8f8);
}
And I wish to change its value when I press on a button
I imported the dart file in my new file and iniside the onpressed function of my button I have:
MaterialApp(
home: Scaffold(
backgroundColor: colors.bgColor,
...
onPressed: () {
setState(() {
colors.bgColor = Color(0xff313131);
});
},
The color inside of bgColor.dart does show up but when I try to change it onPressed (also in an stateful widget) it does not change the scaffold color
If scaffold and button are in the same stateful widget
First of all make sure that backgroundColor: colors.bgColor and setState both are in the same stateful widget, it should work(tested)
If scaffold and button are in different widgets
this way is not dependent on setState so you can use it in both stateless and stateful widgets
Change type of color to ValueNotifier<Color>,
optional: name types using UpperCamelCase
class CustomColors {
static ValueNotifier<Color> bgColor = ValueNotifier(const Color(0xfff8f8f8));
}
wrap scaffold with a ValueListenableBuilder and access color with value property
MaterialApp(
home: ValueListenableBuilder<Color>(
valueListenable: CustomColors.bgColor,
builder: (context, value, child) => Scaffold(
backgroundColor: value,
to change color:
onPressed: () {
CustomColors.bgColor.value = Colors.red;
}
You can use Shared preferences
https://pub.dev/packages/shared_preferences/install
Take a quick look for how to use from anywhere
you can do using the Provider Plugin this.
Color changeColor = colors.bgColor
MaterialApp(
home: Scaffold(
backgroundColor: changeColor,
...
onPressed: () {
setState(() {
changeColor = Color(0xff313131);
});
},

Display full screen loading widget in flutter

I want to show full-screen loading view in flutter while API calling. But when I add loading widget in scaffold body it appears after appbar and bottom navigator.
I am spending lots of time for display loading view in full-screen. Also, I want to prevent back action while API calling.
Well, since you're using Scaffold, then make use of its showDialog() method.
It has a property called barrierDismissible that if you set as false, the user won't be able to close or interact with the screen outside of it.
void _openLoadingDialog(BuildContext context) {
showDialog(
barrierDismissible: false,
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: CircularProgressIndicator(),
);
},
);
}
Once you're done with the API loading, call Navigator.pop(context); to dismiss the dialog.
To prevent the user from clicking the back button on the Dialog, dismissing it, envelop your Scaffold inside a WillPopScope widget and implement the onWillPop function.
#override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
body: Container(),
),
onWillPop: _onBackButton
);
}
Future<bool> _onBackButton() {
// Implement your logic
return Future.value(false);
}
If you return false on it, the user won't be able to press the back button. So use any logic you desire, e.g 'If I'm loading return false, otherwise return true'.
Full-screen loader: Best solution to display a full screen loader is to use package
https://pub.dev/packages/screen_loader.
Prevent back action while loading: This package this package provides a variable isLoading, use it to prevent navigating back. eg:
// --------------- some_screen.dart ---------------
import 'package:flutter/material.dart';
import 'package:screen_loader/screen_loader.dart';
class SomeScreen extends StatefulWidget {
#override
_SomeScreenState createState() => _SomeScreenState();
}
class _SomeScreenState extends State<SomeScreen> with ScreenLoader<SomeScreen> {
getData() {
this.performFuture(NetworkService.callApi);
}
#override
Widget screen(BuildContext context) {
return WillPopScope(
child: Scaffold(
// your beautiful design..
),
onWillPop: () async {
return !isLoading;
},
);
}
}
// --------------- app_api.dart ---------------
class NetworkService {
static Future callApi() async {
}
}
NOTE: You'll need to see the definition of performFuture to see how it works for different scenarios.
You can use this dialog for full screen loading progress_dialog
Did you not think of removing the Scaffold for the loading screen?

Disabling Flutter Hero reverse animation

Given 2 routes, e.g. parent and a child and a Hero(..) widget with the same tag.
When the user is on the "parent" screen and opens a "child" - the Hero widget is animated. When it goes back (via Navigator.pop) it's also animated.
I'm looking for a way to disable that animation when going back (from child to parent via Navigator.pop).
Is there a kind of handler which will be called on a widget before it's going to be "animated away" ? Then I probably could change Hero tag and problem solved.
Or, when creating a "builder" for a route in parent widget, I could probably remember a reference to a target widget and before calling Navigator.pop notify it about "you are gonna be animated out". That would also require making that widget stateful (I haven't found a way to force rebuild a stateless widget).
Is there an easier way of implementing this?
While there currently isn’t a built-in way to disable Hero animations in any particular direction, though CLucera’s use of FadeTransition with HeroFlightDirection is one creative way, the most direct approach is to break the tag association between the two Hero’s:
When you go from the 2nd Hero back to the 1st Hero, just temporarily change the 1st Hero’s tag to something else, then the Hero won’t animate back. A simplified example:
class _MyHomePageState extends State<MyHomePage> {
String tag1, tag2;
String sharedTag = 'test';
String breakTag = 'notTest';
#override
void initState() {
super.initState();
tag1 = sharedTag;
tag2 = sharedTag;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Hero(
tag: tag1,
child: RaisedButton(
child: Text("hi"),
onPressed: () {
// restore the tag
if (tag1 != sharedTag) {
setState(() {
tag1 = sharedTag;
});
}
// second route
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
alignment: Alignment.topLeft,
child: Hero(
tag: tag2,
child: RaisedButton(
child: Text('hello'),
onPressed: () {
// change the tag to disable the reverse anim
setState(() {
tag1 = breakTag;
});
Navigator.of(context).pop();
},
),
)
),
);
}
)
);
},
)
),
),
);
}
}
But if you want to directly modify the animation, then playing around inside the flightShuttleBuilder is the way to do it like CLucera did. You can also check out medium/mastering-hero-animations-in-flutter to further explore that area.
The only approach that I can come up at the moment is to "Animate" the popping Hero in a way that seems not animated, let's check this code:
class SecondRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Hero(
flightShuttleBuilder: (context, anim, direction, fromContext, toContext) {
final Hero toHero = toContext.widget;
if (direction == HeroFlightDirection.pop) {
return FadeTransition(
opacity: AlwaysStoppedAnimation(0),
child: toHero.child,
);
} else {
return toHero.child;
}
},
child: FlatButton(
child: Text("prev 1"),
onPressed: () {
Navigator.of(context).pop();
},
),
tag: "test",
));
}
}
in your SecondRoute (the one that should pop) you have to supply a flightShuttleBuilder parameter to your Hero then you can check the direction and if it is popping, just hide the Widget with an AlwaysStoppedAnimation fade transition
the result is something like this:
I hope that this is something like the expected result, of course, you can completely change the transition inside the flightShuttleBuilder to change the effect! it's up to you :)

Can someone explain to me what the Builder Class does in Flutter?

The documentation is very confusing and vague. Here is what it states:
Builder class
A platonic widget that calls a closure to obtain its child widget.
Here are my questions:
What do they mean by "platonic"?
What do they mean by "closure"?
What exactly is the purpose of this class?
After long hours of extensive hair-pulling research on the internet, I collected small snippets and combined them to put a cohesive and clear explanation of what the Builder Class does.
Terminology:
According to the official flutter documentation, the Builder Class is defined as:
A platonic widget that calls a closure to obtain its child widget.
Platonic means the simplest possible thing of that kind. The term closure is just another name for a lambda function.
Purpose:
This is going to be a lengthy explanation, but please bare with me:
In the Flutter framework, every widget has a build method that accepts a BuildContext parameter:
Widget build (
BuildContext context
) {
... }
We have to remember that the context object is passed to the widget's build function automatically by the framework. Since the framework takes care of that automatically, there is no reason for any widget to have a constructor or function (aside from build) that would need to accept a context parameter.
Hence, if you were trying to pass a specific context object to a child, you won't be able to. You cannot call build() and pass your own context object manually. I mean, you can, but you would be calling the build function twice:
Your manual call.
The automatic call by the framework.
So, how can we pass a specific context object? This is where the Builder class comes in. The purpose of the Builder class is simply to build and return child widgets. How is that different from any other widget? Aha! The Builder class allows you to pass a specific context object down to its children. The Builder class is basically your own build function that you setup.
Why would I need to pass a specific context object? Lets take a look at an example:
Lets say that we want to add a new SnackBar widget to its new Scaffold parent widget that is being returned:
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(),
/// Scaffold doesn't exist in this context here
/// because the context thats passed into 'build'
/// refers to the Widget 'above' this one in the tree,
/// and the Scaffold doesn't exist above this exact build method
///
/// This will throw an error:
/// 'Scaffold.of() called with a context that does not contain a Scaffold.'
floatingActionButton: new FloatingActionButton(onPressed: () {
Scaffold.of(context).showSnackBar(
new SnackBar(
content: new Text('SnackBar'),
),
);
}));
}
This code above does not work. The Scaffold.of(context) function will not find the Scaffold because:
The Scaffold widget has not been created yet.
The context object that was passed to the build function refers to the parent widget, which is not a Scaffold widget.
So, how do we give the child SnackBar widget access to the parent Scaffold widget? We use a Builder class to pass the context of the Scaffold widget:
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Container(),
/// Builders let you pass context
/// from your *current* build method
/// Directly to children returned in this build method
///
/// The 'builder' property accepts a callback
/// which can be treated exactly as a 'build' method on any
/// widget
floatingActionButton: new Builder(builder: (BuildContext context) {
return new FloatingActionButton(onPressed: () {
Scaffold.of(context).showSnackBar(
new SnackBar(
backgroundColor: Colors.blue,
content: new Text('SnackBar'),
),
);
});
}),
);
}
Remember, the Builder class constructor:
Builder({Key key, #required WidgetBuilder builder })
creates a widget by delegating its build to the callback function passed through its constructor.
So, in the code:
new Builder(builder: (BuildContext context){ ... });
we provided a closure that:
Contains a BuildContext context parameter
Builds and returns child widget(s) based on that context passed.
Basically, you provided your own build function. The BuildContext context parameter in this closure is the Scaffold's context! Baboom!
That is basically it. The Flutter documentation does not provide a thorough explanation of this at all. I feel like I would have an easier time deciphering ancient hieroglyphs than decoding the Flutter documentation.
SUMMARY: For anyone still having a hard time grasping this concept, let me explain in a more concise form. The Builder function simply allows you to attain and use the context object of the widget that the Builder widget is in. In the example above, it is the new Scaffold() widget. Remember, the only context object available to use is that of the parent widget (above Scaffold) since the current widget (Scaffold) has not been created yet. I hope that helps those were still scratching their heads.
It basically converts a function that builds a widget into a widget.
Wo where you need to pass a widget but only have a function that returns a widget, you can use the Builder widget.
bool bar;
Widget createFooOrBarWidget() {
if(bar) {
return BarWidget();
}
return FooWidget();
}
Widget build(BuildContext context) =>
Container(child: Builder((context) => createFooOrBarWidget()));
you could also use
Widget build(BuildContext context) =>
Container(child: createFooOrBarWidget());
but the former delays the creation of the Foo or Bar widget until build is actually called.
Simple Definition
This Builder widget is, like its name is implying; used to create a child widget with a "new context".
Technical Definition
This Builder has a builder property and this property accept WidgetBuilder typedef (WidgetBuilder typedef is a Signature for a function that creates(return) a widget with new context)
If you want to know about that WidgetBuilder typedef please use this link ➡https://api.flutter.dev/flutter/widgets/WidgetBuilder.html
usage:
1. when Scaffold widget and Scaffold.of method are in same build method.
[ At that time scaffold.of method cannot find closet Scaffold widget ,because both are
in
same context ,by creating new context inside build method you can solve this, that why
we use Builder Widget to create widget with New BuildContext. ]
Below code shows real usage of Builder widget ,when you want use Scaffold.of and Scaffold widget in same build method
(Look these comments carefully-it will help you to understand the context)
Widget build(BuildContext context) { // context - 1
return Scaffold(
appBar: AppBar(
title: Text('Demo')
),
body: Builder(
// Create an inner BuildContext so that the onPressed methods
// can refer to the Scaffold with Scaffold.of().
builder: (BuildContext context) { // context - 2
return Center(
child: RaisedButton(
child: Text('SHOW A SNACKBAR'),
onPressed: () {
Scaffold.of(context).showSnackBar(SnackBar( // here context is (context- 2)
content: Text('Have a snack!'),
));
},
),
);
},
),
);
}
2. When Theme.of method and Theme widget in a same build method.
[here also purpose same as above 1]
Below code shows real usage of Builder widget ,when you want use Theme.of and Theme widget in same build method
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.light(),
body: Builder(
// Create an inner BuildContext so that we can refer to
// the Theme with Theme.of().
builder: (BuildContext context) {
return Center(
child: Text(
'Example',
style: Theme.of(context).textTheme.title,
),
);
},
),
);
}
EXTRA POINT
buider property (WidgetBuilder typedef) we can see on many instances
Down below that code part shows, 'how MaterialPageRoute ' use builder property to get widget for that route
Navigator.push(context, MaterialPageRoute<void>(
builder: (BuildContext context) { //here
return Scaffold(
appBar: AppBar(title: Text('My Page')),
body: Center(
child: FlatButton(
child: Text('POP'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
},
));

Flutter Drawer lazy load when extracted to custom widget

I have a drawer on my main page which became bloated with content and logic. For example, I need to fetch an image, get some data from Internet and so on. After extracting Drawer in new MyCustomDrawerWidget which extends StatefulWidget, its state has build function which looks like this:
#override
Widget build(BuildContext context) {
return Drawer(
child: ...
);
}
My HomePageState has build function which looks like this:
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home page'),
),
drawer: MyCustomDrawerWidget(),
body: ...
);
}
After refactoring the code like this, I noticed how drawer does not get initialized when page loads. It would initialize just after I open the drawer, which is bad because user needs to wait for data to be loaded.
Is there a way to set eager loading on drawer to get initialized along with the page?
I had the same problem. The Scaffold only attaches the drawer when necessary.
The solution: Move the data loading logic into the parent widget. You can still encapsulate it in a separate model class (e.g. DrawerModel), but this class must be instantiated when the parent widget is initialized. Keep it in the State of the parent. Pass DrawerModel to your drawer widget.
You have to be a bit careful when using streams and futures, because those usually can not be resubscribed. I would recommend you to use a StreamBuilder in the drawer widget that is connected to a BehaviorSubject in your DrawerModel.
That's very much the BLoC pattern. Do you need a code example?
Problem with having Drawer in MainPage:
Content, more logic, fetch an image, get some data from the Internet and so on.
Problem with having Drawer in MyCustomDrawerWidget:
Loading time, bad user experience
I would suggest getting the hybrid approach to solve the problem.
Trigger the network calls in MainPage
Send future to the MyCustomDrawerWidget (If the user opens drawer before network call completes, we can show loader based on future value)
Move the logic and contents related to drawer into MyCustomDrawerWidget.
class MyCustomDrawerWidget extends StatelessWidget {
var _future;
MyCustomDrawerWidget(this._future);
#override
Widget build(BuildContext context) {
return new Drawer(
child: FutureBuilder(
builder: (context, snapshot) {
if (snapshot.hasData) {
var data = snapshot.data;
return new Text(data);
} else {
return new Text("loading");
}
},
future: _future,
),
);
}
}
And in MainPage
#override
Widget build(BuildContext context) {
val future = Future.delayed(Duration(seconds: 10), () => "new value") //network call
return Scaffold(
appBar: AppBar(
title: Text('Home page'),
),
drawer: MyCustomDrawerWidget(future),
body: ...
);
}
Hope this helps.

Categories

Resources