Reusable card widget with flutter - flutter

I'm trying to replicate an Android app with Dart/Flutter that I already have a web version of written with PHP and Laravel. It has multiple sequentials screens that have the same components, like appbar, float button and a card to display contents of each screen. Using blade templates, is it possible to do something like:
<div class="card">
#yeld('card_content')
</div>
And use it in others views with:
#section('card_content')
<div class="table">
{{$data}}
</div>
#endsection
I know how to set a variable data in the constructor of the widget to be displayed, but how could I invoke a card widget and add children widgets to it, like in Laravel? Or should I copy the card code in every screen?

You have to define a new widget yourself and reuse the new widget whenever you need it. So just create a new Widget and return the card in the build function with the design you want to have it. For Example:
class MyPersonalCard extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Card(color: Colors.red, child: Text('This is a reusable card'),);
}
}
Now you could use MyPersonalCard() as widget like you would use your Card() widgets at the places you want them to be. You could also make the color still customizable if you do something like this:
class MyPersonalCard extends StatelessWidget {
final Color color;
const MyPersonalCard({Key key, this.color = Colors.red}) : super(key: key);
#override
Widget build(BuildContext context) {
return Card(color: this.color, child: Text('This is a reusable card'),);
}
}

Related

Flutter Reorder-able Widgets Size

I am trying to create a Flutter page with reorder-able widgets of different sizes. The closest analogy is like Android/iOS home screens with widgets.
Both of these OS' can be have apps (1x1 size), small widgets (4x1), and even large widgets (4x4) on the same page. More so, when a large app takes up the full width of a page, it re-orders the widgets below/above it as to not interfere with the UI.
I am trying to do the same with Flutter, and hopefully make is so that user's can move widgets within my application the same way they do with their mobile operating systems.
The most similar StackOverFlow question to this one can be found here -- but its quite different.
Prerequisite packages:
reorderable_widgets.dart
custom_sizes.dart
The closest I got so far is with these two packages. But, I can't seem to figure the next steps after making this:
// imported packages
import 'package:flutter/material.dart';
import 'custom_sizes.dart';
import 'reorderable_widgets.dart';
class Demo extends StatelessWidget {
const Demo({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return const Scaffold(
backgroundColor: Colors.grey,
body: CustomWidgetGrid(),
);
}
}
class CustomWidgetGrid extends StatefulWidget {
const CustomWidgetGrid({Key? key,}) : super(key: key);
#override
State<CustomWidgetGrid> createState() => _CustomWidgetGridState();
}
class _CustomWidgetGridState extends State<CustomWidgetGrid> {
final items1 = fillWithCustomWidgets1();
final items2 = fillWithCustomWidgets2();
final items3 = fillWithCustomWidgets2();
#override
Widget build(BuildContext context) {
return CustomScrollView(
clipBehavior: Clip.hardEdge,
slivers: <Widget>[
CustomSliverReorderableGrid(maxExtent: MediaQuery.of(context).size.width/(items2.length), children: items2,),
CustomSliverReorderableGrid(maxExtent: MediaQuery.of(context).size.width/items1.length, children: items1,),
],
);
}
}
This has already solved:
Having responsive, moveable widgets that save their end state
Providing padding around each widget
But the problem that I'm running at is that all widgets are the same size. Messing with the source code for the package has been a bit of pain, and I think that there is any easy solution that I missed (or don't know about).
Any thoughts would be greatly appreciated!

cannot navigate next page

Does anyone know why my navigation page isnt working after I click the icon Parking which will lead me to other pages which will display an appbar.
I can not test your code but I think this happens because you are trying to navigate between two different app roots. Try removing the MaterialApp widget from your "parkingscreen.dart" file. Keep only the Scaffold widget since it contains all material components for you to work. Let me know if it works!
Please have this kind of code in your ParkingScreen.dart file
class ParkingScreen extends StatelessWidget {
const ParkingScreen({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Parking Screen')),
body: SafeArea(child: Column()),
);
}
}
This happens because you are trying to navigate between two different app roots

Pass parent state to generic child widget

I'm building a flutter app, and I have built a customized AppBar widget for it. This appbar has a SearchBar widget, which calls whatever callback is passed to it onChange. Now, there are multiple screens that use this SearchBar, and each of them will do something different with the user input. But I've noticed that on each of the screens that use the appbar, I'd have to use a state to control the SearchBar inputted text. So, I'm trying to not have to create the state for every screen, and have a Widget that wraps my screens, and controls the input in it's state, and passes it's state down to the child I provide to this apps. This would be similar to React's Higher Order Components, which wrap another component and can pass props to it.
This seems to me like a good design pattern, but I don't know how to implement it. Since the child widget that I would pass to this second order component that would wrap my screens, won't be getting any info from it, the child is simply passed as a widget (note: the code is also doing other stuff, working as a general wraper to replace similar repetitive code in all of my screens):
class CustomScaffold extends StatefulWidget {
final ScreenInfo appBarInfo;
final Builder child;
final Widget bottomBarApp;
final Widget appBar;
final EdgeInsetsGeometry padding;
const CustomScaffold({
#required this.child,
Key key,
this.bottomBarApp,
this.appBar,
this.padding,
this.appBarInfo,
}) : super(key: key);
#override
_CustomScaffoldState createState() => _CustomScaffoldState();
}
class _CustomScaffoldState extends State<CustomScaffold> {
String searchTerm = '';
#override
Widget build(BuildContext context) {
return Scaffold(
body: SingleChildScrollView(
child: Column(
children: [
widget.appBar ??
GradientAppBar(
title: widget.appBarInfo.label,
searchBar: SearchBar(
callback: (String input) {
setState(() {
searchTerm = input;
});
},
placeholder: widget.appBarInfo.searchPlaceholder,
),
),
Padding(
padding: widget.padding,
child: widget.child,
),
],
),
),
bottomNavigationBar: widget.bottomBarApp ?? CustomBottomBarNavigator(),
);
}
}
I'm thinking that I could passa builder instead of a widget as the child, but I'm not sure how this would work. Also, I'm still learning bloc in general, so I'm not sure if it would be a good idea to use bloc for this. I'm guessing bloc's purpose is a little different, and would complicate this specific pattern.
Does this idea make sense? What would be the best way to implement it?
Thanks in advance.

Preserve widget state when temporarily removed from tree in Flutter

I'm trying to preserve the state of a widget, so that if I temporarily remove the stateful widget from the widget tree, and then re-add it later on, the widget will have the same state as it did before I removed it. Here's a simplified example I have:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#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> {
bool showCounterWidget = true;
#override
Widget build(BuildContext context) {
return Material(
child: Center(
// Center is a layout widget. It takes a single child and positions it
// in the middle of the parent.
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
showCounterWidget ? CounterButton(): Text("Other widget"),
SizedBox(height: 16,),
FlatButton(
child: Text("Toggle Widget"),
onPressed: (){
setState(() {
showCounterWidget = !showCounterWidget;
});
},
)
],
),
),
);
}
}
class CounterButton extends StatefulWidget {
#override
_CounterButtonState createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
int counter = 0;
#override
Widget build(BuildContext context) {
return MaterialButton(
color: Colors.orangeAccent,
child: Text(counter.toString()),
onPressed: () {
setState(() {
counter++;
});
},
);
}
}
Ideally, I would not want the state to reset, therefor the counter would not reset to 0, how would I preserve the state of my counter widget?
The reason why the widget loose its state when removed from the tree temporarily is, as Joshua stated, because it loose its Element/State.
Now you may ask:
Can't I cache the Element/State so that next time the widget is inserted, it reuse the previous one instead of creating them anew?
This is a valid idea, but no. You can't.
Flutter judges that as anti-pattern and will throw an exception in that situation.
What you should instead do is to keep the widget inside the widget tree, in a disabled state.
To achieve such thing, you can use widgets like:
IndexedStack
Visibility/Offstage
These widgets will allow you to keep a widget inside the widget tree (so that it keeps its state), but disable its rendering/animations/semantics.
As such, instead of:
Widget build(context) {
if (condition)
return Foo();
else
return Bar();
}
which would make Foo/Bar loose their state when switching between them
do:
IndexedStack(
index: condition ? 0 : 1, // switch between Foo and Bar based on condition
children: [
Foo(),
Bar(),
],
)
Using this code, then Foo/Bar will not loose their state when doing a back and forth between them.
Widgets are meant to store transient data of their own within their scope and lifetime.
Based on what you have provided, you are trying to re-create CounterButton child widget, by removing and adding it back to the widget tree.
In this case, the counter value that is under the CounterButton was not saved or not saving in the MyHomePage screen, the parent widget, without any reference to a view model or any state management within or at the top level.
A more technical overview how Flutter renders your widgets
Ever wonder what is the key if you try to create a constructor for a widget?
class CounterButton extends StatefulWidget {
const CounterButton({Key key}) : super(key: key);
#override
_CounterButtonState createState() => _CounterButtonState();
}
keys (key) are identifiers that are automatically being handled and used by the Flutter framework to differentiate the instances of widgets in the widget tree. Removing and adding the widget (CounterButton) in the widget tree resets the key assigned to it, therefore the data it holds, its state are also removed.
NOTE: No need to create constructors for the a Widget if it will only contain key as its parameter.
From the documentation:
Generally, a widget that is the only child of another widget does not need an explicit key.
Why does Flutter changes the key assigned to the CounterButton?
You are switching between CounterButton which is a StatefulWidget, and Text which is a StatelessWidget, reason why Flutter identifies the two objects completely different from each other.
You can always use Dart Devtools to inspect changes and toggle the behavior of your Flutter App.
Keep an eye on #3a4d2 at the end of the _CounterButtonState.
This is the widget tree structure after you have toggled the widgets. From CounterButton to the Text widget.
You can now see that the CounterButton ending with #31a53, different from the previous identifier because the two widgets are completely different.
What can you do?
I suggest that you save the data changed during runtime in the _MyHomePageState, and create a constructor in CounterButton with a callback function to update the values in the calling widget.
counter_button.dart
class CounterButton extends StatefulWidget {
final counterValue;
final VoidCallback onCountButtonPressed;
const CounterButton({Key key, this.counterValue, this.onCountButtonPressed})
: super(key: key);
#override
_CounterButtonState createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
#override
Widget build(BuildContext context) {
return MaterialButton(
color: Colors.orangeAccent,
child: Text(widget.counterValue.toString()),
onPressed: () => widget.onCountButtonPressed(),
);
}
}
Assuming you named your variable _counterValue in the _MyHomePageState, you can use it like this:
home_page.dart
_showCounterWidget
? CounterButton(
counterValue: _counterValue,
onCountButtonPressed: () {
setState(() {
_counterValue++;
});
})
: Text("Other widget"),
In addition, this solution will help you re-use CounterButton or other similar widgets in other parts of your app.
I've added the complete example in dartpad.dev.
Andrew and Matt gave a great talk how Flutter renders widgets under the hood:
https://www.youtube.com/watch?v=996ZgFRENMs
Further reading
https://medium.com/flutter-community/flutter-what-are-widgets-renderobjects-and-elements-630a57d05208
https://api.flutter.dev/flutter/widgets/Widget/key.html
The real solution to this problem is state management. There are several good solutions for this available as concepts and flutter packages. Personally I use the BLoC pattern regularly.
The reason for this is that widget state is meant to be used for UI state, not application state. UI state is mostly animations, text entry, or other state that does not persist.
The example in the question is application state as it is intended to persist longer than the live time of the widget.
There is a little Tutorial on creating a BLoC based counter which could be a good starting point.

I want to avoid to recreate the AppBar and Drawer when I change the tool that I am using

I am creating an app that has by default the google map satellite view at the center of the application, but I have other tools in my app, so I would like to keep the same AppBar and the same Drawer in all screens, avoiding to create it over and over again. The only thing that I would like to change is the satellite view so that the space occupied by the map would fit another tool's functionalities.
Is there a way to create only once the AppBar and Drawer and fit other tools' screens in the space of the map without needing to recreate the AppBar and Drawer many times?
Thanks!
Within a scaffold, set the body to be an AnimatedSwitcher, and have the child be a variable. Any time you wish to change the satellite view out for another widget, call SetState and change the child variable.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: MyAppBar(), //This will never change
body: DynamicContainer(),
);
}
ChangeWidget() {
myChangingWidget = Container();
}
}
class DynamicContainer extends StatefulWidget {
DynamicContainer({Key key}) : super(key: key);
_DynamicContainerState createState() =>
_DynamicContainerState(controller: controller);
}
class _DynamicContainerState extends State<DynamicContainer> {
Widget _myAnimatedWidget;
#override
void initState() {
super.initState();
_myAnimatedWidget = myListOfWidgets[0]; //The widget it will start as
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return AnimatedSwitcher(
duration: Duration(milliseconds: 250),
child: _myAnimatedWidget, //Our dynamic widget
);
}
switchWidget() {
setState(() {
_myAnimatedWidget =
myListOfWidgets[1]; //Where you change the widget
});
}
}
This will redraw the body while maintaining your AppBar and Drawer. Check out the official Flutter video on AnimatedSwitcher - https://youtu.be/2W7POjFb88g
**Code Snippet has been updated and should be correct.