Flutter: In ReorderableListView: Cannot find the correct Provider<...> when dragging - flutter

I have my theme class that I hang in the widget tree using Provider:
Scaffold(
body: Provider<AppControlsThemeData>(
create: (_) => ...,
child: ...
),
);
Then somewhere below I read it with
final themeData = Provider.of<AppControlsThemeData>(context);
It works alright until I try to use this lookup in ReorderableListView. When I drag the widget that does the lookup, it fails to find the provider:
How do I hang some object in the widget tree so that its lookup works while dragging? I hang it as far as it is reasonable -- right under the Scaffold. Looks like this draggable widget gets detached from my screen's tree and I cannot control what is above it in its new tree.

Related

How to set state a class from another class in flutter?

I'm trying to implement dark mode in my flutter app using provider, but I'm having some issues:
I have a home screen that it has a scaffold widget. The body of the scaffold is a Stack with two different classes, as you can see here:
#override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: Constant.menuOn
? BorderRadius.circular(60.0)
: BorderRadius.circular(0.0),
child: Scaffold(
body: Stack(
children: const [
Menu(),
HomeScreen(),
],
),
),
);
}
The home screen class has another Scaffold widget, where hos body has another class called body.
It is from the body where I'm able to change the theme of my app using a switch where I can set state my BODY class with the provider as you can see here:
lsetState(() {
final provider = Provider.of<ThemeProvider>(
context,
listen: false);
rovider.toggleTheme(Constant.isDarkMode);
});
My body class change the theme however the class that is under the body class (Remember that my class "MENU" is under the class BODY because the first stake), does not change theme until restart my app (I'm using the same parameters of the body in order to know when has to change the class)...
I want to know how I can set the theme state of my menu class, or in general how I can change any other state from any class.
Note: Both of my class are StatefulWidget.
In general, setState is a very local intra-widget operation. Local data has changed, so the local view needs updating. Wanting to call the setState of another widget is a bad code smell. If you need other views depending on data you've updated, consider one of the state management solutions. (I would, for example, use a Riverpod Provider to hold the data, then watch that in every dependent view.)

How to dynamically disable vertical swipe in PageView

I would like to find a way to update the physics param to disable the swipe action in a parent PageView from a child widget.
I am using riverpod for updating the state in a child widget when it builds to know when I should pass NeverScrollableScrollPhysics to the physics param in a parent widget. But the thing is that this approach is causing my child widget to rebuild recursively, because this is making the PageView rebuild to update the physics param. So I really don't know what to do here.
I have this parent Widget that builds the PageView:
Widget build(BuildContext context) {
final _navBar = useProvider(bottomNavigationBarProvider);
return PageView(
physics: navBar.isSwipeBlocked ? const NeverScrollableScrollPhysics() : null,
controller: pageController,
onPageChanged: onPageChanged,
children: [
Beamer(
key: const Key('feed-tab'),
routerDelegate: BeamerDelegate(locationBuilder: (state) => FeedLocation(state)),
),
]
)
}
And the child Widget that updates the state variable:
Widget build(BuildContext context) {
final _navBar = useProvider(bottomNavigationBarProvider.notifier);
useEffect(() {
Future.microtask(() => _navBar.blockSwipe());
}, []);
return Container(...);
}
So when FeedLocation loads, it updates _navBar in an attempt for disabling the scroll behavior. But as I mentioned, this causes the parent to rebuild and FeedLocation to build again and then the recursive state..
The idea was to be able to go to FeedLocation, disable the scroll, then when go back, enable it again, but I don't see a solution for that.
I think I did already what this guy suggested https://github.com/flutter/flutter/issues/37510#issuecomment-738051469 using Riverpod
And I guess I am a similar situation as this guys from the same thread https://github.com/flutter/flutter/issues/37510#issuecomment-864416592
Is anybody able to see a solution or what I am doing wrong?
You should replace null as the secondary ScrollPhysics with PageScrollPhysics() and make sure to add setState(() {}); when you update the isSwipeBlocked variable.

Flutter - What's the best practice to create a fixed AppBar during navigation

In native android development, it is common to use FragmentTransaction to create a navigation animation, where the actionBar's position stays fixed (but actionBar's content changed), and the fragment beneath actionBar performs a transition animation (like slide in or out).
To put it simple, the AppBar and the body performs different transition animation. In flutter, what is the best practice to create such animation?
Currently I can think of a solution of using a navigation in Scaffold.body and using Stream + StreamBuilder to start AppBar redraw. Something like the following code.
Scaffold(
appBar: StreamBuilder<Int>(
stream: Bloc.of(context).currentFragmentId
builder: //Some logic to decide which AppBar is appropriate
),
body: Navigator(
//Navigation between fragments
),
)
But this is really weird. So is there a best practice to do this? Please let me know!
Well, since there is currently no answer availble. I'm going to share my solution here, though it is not so elegant.
The solution is to wrap AppBar in a Hero widget. Since Scaffold only takes a PreferedSize widget as an appbar, some customization is required.
class HeroAppBar extends StatelessWidget implements PreferredSizeWidget {
//...
#override
final Size preferredSize = Size.fromHeight(kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0));
//...
#override
Widget build(BuildContext context) {
return Hero(
tag: tag,
child: AppBar(
//...
),
);
}
}
And make sure your Navigator implemented the HeroController. (Default Navigator has already implemented it)

Adding OverlayEntry in Flutter

I am trying to insert a Container to the Overlay, but I had an error with this code.
class _MyHomePageState extends State<MyHomePage> {
#override
void didChangeDependencies() {
super.didChangeDependencies();
final entry = OverlayEntry(builder: (BuildContext overlayContext) {
return Container(
height: 50.0,
width: 50.0,
color: Colors.blue,
);
});
_addOverlay(entry);
}
void _addOverlay(OverlayEntry entry) async {
Overlay.of(context).insert(entry);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Flutter'),
),
body: Center(),
);
}
}
This is error
setState() or markNeedsBuild() called during build. This Overlay widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase...
Thank you in advance.
Since the last update to flutter 0.8.1 I noticed this change too. I fixed this to add the overlay after a minimal delay
Timer.run(() { Overlay.of(context).insert(calendarOverlay);});
Now this works but it feels like a hack...
So in my build i use this code when the overlay should present itself..
If anyone has a better solution, I am interested ;-)
John
UPDATE: I found this code to be working too:
final overlay = Overlay.of(context);
WidgetsBinding.instance.addPostFrameCallback((_) => overlay.insert(entry));
It saves me from including timers...
Just share some of my findings. I am about to implement overlay in my app too. So found this SO question by searching.
Many people build overlay before the normal widget. For example, in your code, the overlay insert in didChangeDependencies is called before building the Scaffold. This is the cause of all the async problems. I found people do this (couple the overlay insert and corresponding normal widget in a stateful widget) is because they want to find the corresponding child widget's position, but the child widget is build after the overlay insert call, thus the overlay insert has to be in an async function.
But If you just call overlay insert after building the normal widget (make overlay insert call independent from building the base widget. Separate/decouple them), you won't need any async or Timer functions at all. In my current implementation, I separate them just to make the code safe (I feel it's safer). So no need for any async calls.

InheritedWidget with Scaffold as child doesn't seem to be working

I was hoping to use InheritedWidget at the root level of my Flutter application to ensure that an authenticated user's details are available to all child widgets. Essentially making the Scaffold the child of the IW like this:
#override
Widget build(BuildContext context) {
return new AuthenticatedWidget(
user: _user,
child: new Scaffold(
appBar: new AppBar(
title: 'My App',
),
body: new MyHome(),
drawer: new MyDrawer(),
));
}
This works as expected on app start so on the surface it seems that I have implemented the InheritedWidget pattern correctly in my AuthenticatedWidget, but when I return back to the home page (MyHome) from elsewhere like this:
Navigator.popAndPushNamed(context, '/home');
This call-in the build method of MyHome (which worked previously) then results in authWidget being null:
final authWidget = AuthenticatedWidget.of(context);
Entirely possible I'm missing some nuances of how to properly implement an IW but again, it does work initially and I also see others raising the same question (i.e. here under the 'Inherited Widgets' heading).
Is it therefore not possible to use a Scaffold or a MaterialApp as the child of an InheritedWidget? Or is this maybe a bug to be raised? Thanks in advance!
MyInherited.of(context) will basically look into the parent of the current context to see if there's a MyInherited instantiated.
The problem is : Your inherited widget is instantiated within the current context.
=> No MyInherited as parent
=> crash
The trick is to use a different context.
There are many solutions there. You could instantiate MyInherited in another widget, so that the context of your build method will have a MyInherited as parent.
Or you could potentially use a Builder to introduce a fake widget that will pass you it's context.
Example of builder :
return new MyInheritedWidget(
child: new Builder(
builder: (context) => new Scaffold(),
),
);
Another problem, for the same reasons, is that if you insert an inheritedWidget inside a route, it will not be available outside of this route.
The solution is simple here !
Put your MyInheritedWidget above MaterialApp.
above material :
new MyInherited(
child: new MaterialApp(
// ...
),
)
Is it therefore not possible to use a Scaffold or a MaterialApp as the
child of an InheritedWidget?
It is very possible to do this. I was struggling with this earlier and posted some details and sample code here.
You might want to make your App-level InheritedWidget the parent of the MaterialApp rather than the Scaffold widget.
I think this has more to do with how you are setting up your MaterialWidget, but I can't quite tell from the code snippets you have provided.
If you can add some more context, I will see if I can provide more.