Flutter Reorder-able Widgets Size - flutter

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!

Related

Flutter PageView progressively uses more memory

In a flutter PageView containing high-resolution images, as I swipe to new images, flutter uses more and more memory. How do I fix it?
Here is my main.dart file.
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
final List<String> urls = [
"https://unsplash.com/photos/Eelegt4hFNc/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1NzA4NTgy&force=true",
"https://unsplash.com/photos/MI9AqYWeM24/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1NzA4Njk1&force=true",
"https://unsplash.com/photos/kFHz9Xh3PPU/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1NzA3NDU3&force=true",
"https://unsplash.com/photos/_AjqGGafofE/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1Njg0MDMx&force=true",
"https://unsplash.com/photos/8Qr1ixi-rMU/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1NzA0NTkx&force=true",
"https://unsplash.com/photos/xaZSE0h7yIY/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1NzA4MTY1&force=true",
"https://unsplash.com/photos/RbRWDUyDEWQ/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1NzA4NzY5&force=true",
"https://unsplash.com/photos/HJCywuQqKYY/download?ixid=MnwxMjA3fDB8MXxhbGx8fHx8fHx8fHwxNjc1NzA4Nzcx&force=true"
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: PageView.builder(
itemCount: urls.length,
itemBuilder: (context, index) {
return Image(image: NetworkImage(urls[index]));
},
),
);
}
}
After swiping through the images flutter uses around 1.7 GB memory.
Here is the memory graph
By default, Flutter caches images. I've found that in order to avoid app termination by OOM killer, it is sometimes necessary to be more hands-on with image memory management.
In your case, you may benefit from explicitly calling the NetworkImage.evict() method, which is inherited from ImageProvider.
There are other strategies, too. This answer shows how to override the image cache. You can implement your own customizations, such as never caching more than N images.
See also the ImageCache class.
It may also depend on behavior of PageView. I am not familiar with this widget and if/how it disposes of children which are no longer visible. If it is hanging on to ui.Image references, you may have to go to some special effort.

stateful widget error, A constant constructor can't call a non-constant super constructor of 'State'

here is my public git.
https://github.com/CrazyBunnyz/Sociominer_V2/
there are 2 branches on that git.
the main is my original code with stateless for most of the part. the problem comes when I try to integrate my code with ExpansionTile where I need state to detect on onExpansionChanged. i try to implement the example but I keep getting the error below. can anyone please help. you can see both code before and after the error from both branches.
ps : already tried ChatMember({Key? key, this.deviceScreenType}) : super(key: key); it pop other error.
the full issue can be found in https://github.com/flutter/flutter/issues/118311. can anyone help ?
You need to use widget clsss instead of state class. Remove params from ChatMember and use widget class.
class MyStatefulWidget extends StatefulWidget {
final DeviceScreenType? deviceScreenType;
const MyStatefulWidget({super.key, this.deviceScreenType});
#override
State<MyStatefulWidget> createState() => ChatMember();
}
class ChatMember extends State<MyStatefulWidget> {
#override
Widget build(BuildContext context) {
Now to access widget variable do,
Column(
children: List.generate(
members.length,
(index) => MemberCard(
member: members[index],
deviceScreenType: widget.deviceScreenType, //this
),
),
),
Next issue is you are naming differently and it's confusing,
Your case
MyStatefulWidget is a widget class
ChatMember is state class
You need to use MyStatefulWidget like,
Container(
width: 400,
child: MyStatefulWidget(deviceScreenType: deviceScreenType),
),
You can check the pr

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

How to detach a child from one location in a widget tree and attach it another location with animation in Flutter?

I was not sure how to express this question more clearly. We can consider this use case as a reference example.
Say we have a parent container with a column a it has two children RowWidget1 and RowWidget2. Below is a demo code we can refer.
class DemoWidget extends StatefulWidget {
const DemoWidget({Key? key}) : super(key: key);
#override
_DemoWidgetState createState() => _DemoWidgetState();
}
class _DemoWidgetState extends State<DemoWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
RowWidget1(),
RowWidget2()
],
),
);
}
}
Our RowWidgets will be a basic row one with 3 containers in it and other with 2 containers. My goal is to do something like on tapping the last 3rd container of RowWidget1 it will be shifted to RowWidget2 with animation. Like detaching it from one widget and adding it to another with animation.
The goal is to create something like Hero animation but for our custom widget transition and not Page Navigation.
Below is an demo build of our expected result : ( I've emulated this one using Stack widget )
https://youtu.be/oyuw95LlsBw
Thankyou is advance for any sort of assistance.

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.