Responsive wrapping rows with specific widths and alignments - flutter

I want my flutter app to look a certain way when the phone is tilted vs when it's held normally.
I have created a solution for each state but I can't figure out a solution that works for both at once.
So my question is: how can I achieve the following with one codebase?
- and if it is not easily possible: how can I make my app dynamically decide which layout to use?
Here's a minified example of my app (my generateList* methods are shown below):
void main(List<String> arguments) async {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "Test",
home: Scaffold(
body: ListView(
//children: generateListTilted(),
//or
children: generateListNotTilted(),
),
),
);
}
}
I have rows with two fields of information, the date and the content.
The date does not always have the exact width 'cause the months are spelled out, but it's similar.
The content always has the same length.
Case: phone tilted/wide screen
If there is enough space for both fields in one line, each row should look like it has two colums - the first one having the width of the largest date field, and the second one just filling out the rest of the space and being centered. I didn't know how to adjust the width automatically to the widest date so I just set the flex property as a workaround:
List<Widget> generateListTilted() {
var sameSizeContent = "TEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEST";
var slightlyVariableLengthDates = ["August, 21 2019", "May, 1 2019"];
List<Widget> listOfRows = [];
for (var i = 0; i < slightlyVariableLengthDates.length; i++) {
listOfRows.add(
Container(
padding: const EdgeInsets.all(2.0),
child: Row(
children: <Widget>[
Expanded(
flex: 3,
child: Text(
slightlyVariableLengthDates[i],
textAlign: TextAlign.right,
)),
Expanded(
flex: 7,
child: Text(
sameSizeContent,
textAlign: TextAlign.center,
),
)
],
),
),
);
}
return listOfRows;
}
Case: phone not tilted/small screen
If the date and content do not fit in one line, they should wrap and the date should be on the top left - but again have the width of the longest date and right alignment (I was not able to achieve this so I edited the screenshot to make it clear).
The content should be centered again:
List<Widget> generateListNotTilted() {
var sameSizeContent = "TEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEST";
var slightlyVariableLengthDates = ["August, 21 2019", "May, 1 2019"];
List<Widget> listOfRows = [];
for (var i = 0; i < slightlyVariableLengthDates.length; i++) {
listOfRows.add(
Container(
padding: const EdgeInsets.all(2.0),
child: Row(
children: <Widget>[
Expanded(
child: Wrap(
crossAxisAlignment: WrapCrossAlignment.center,
children: [
Text(
slightlyVariableLengthDates[i],
),
Center(child: Text(sameSizeContent))
],
),
),
],
),
),
);
}
return listOfRows;
}

I want my flutter app to look a certain way when the phone is tilted
vs when it's held normally. I have created a solution for each state
but I can't figure out a solution that works for both at once.
Use MediaQuery to know about your current screen orientation and other device related information.
Example Code:
Container(
alignment: Alignment.center,
child:
(MediaQuery.of(context).orientation == Orientation.portrait)
Text("Portrait")
:
Text("Landscape")
)
Note: I have used ternary operator outside the Text widget to show that ternary operator can be used almost anywhere(Widget,String,parameter values), where we just need to apply small logical UI changes.

If you're looking at making your UI responsive, please checkout the answer to a similar question.

Related

How to align Flutter widgets in Tablecells/Tablerow when using SizedBox to manage row height?

I have Form with Text and TextFormFields in TableRows to capture some user data. Some of the rows are empty depending on the type of data being entered and so I have used SizedBoxes to maintain a fixed height for all rows so that the 'Save' button at the bottom of the form doesn't move about when showing filled/blank rows (this seemed to be the recommended approach based on an internet search).
Before I added the SizedBox widgets, the Text and TextFormFields were aligned nicely by defaultVerticalAlignment: TableCellVerticalAlignment.middle at the Table level. Now that I have added the SizedBox widgets I can't see now how to align these items.
I have searched online, but can't find an answer to this question. The Flutter guidance on TableRow says that, "The alignment of individual cells in a row can be controlled using a TableCell", but I can't see any guidance on how this is achieved. I have tried using TableCellVerticalAlignment, but that doesn't appear to have the desired effect.
Does anyone know how to get these fields to align so that the text in Text widget and text in TextFormField are aligned as per the "Without SizedBox" screen shot below?
Code snippet and screen shots below showing without and with SizedBox widgets.
Code Snippet Showing TableRow and TableCells using SizedBox...
class _InputFormState extends State<InputForm> {
final _formKey = GlobalKey<FormState>();
final double _height = 50;
#override
Widget build(BuildContext context) {
// Build a Form widget using the _formKey created above.
return Form(
key: _formKey,
child: Column(
children: [
Padding(
padding: const EdgeInsets.all(20.0),
child: Table(
defaultVerticalAlignment: TableCellVerticalAlignment.bottom,
columnWidths: const {
0: FlexColumnWidth(1.5),
1: FlexColumnWidth(3)
},
children: [
//
// Instrument name
//
if (widget._record.isInstrument || widget._record.isFrequency)
TableRow(children: [
SizedBox(
height: _height,
child: const TableCell(
verticalAlignment: TableCellVerticalAlignment.bottom,
child: Text('Instrument:'),
),
),
SizedBox(
height: _height,
child: TableCell(
verticalAlignment: TableCellVerticalAlignment.bottom,
child: TextFormField(
initialValue: widget._record.instrument,
maxLines: 1,
inputFormatters: [
FilteringTextInputFormatter.allow(
RegExp(r'[A-Z a-z-]')),
LengthLimitingTextInputFormatter(35),
],
onChanged: (value) {
setState(() {
widget._record.addInstrument = value;
});
},
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter an instrument name';
}
return null;
},
),
),
),
]),
...
Alignment With SizedBox:
Alignment Without SizedBox:
I have managed to do this by wrapping the Text widget within the TableCell with an Align and setting the alignment to Alignment.CenterLeft.
Whilst working this out, I discovered that if a Tablecell doesn't have Table (or presumably TableRow) as a parent then Flutter will thrown an error stating, "Incorrect use of ParentDataWidget Error in Flutter". More information on that error here. For me, this wasn't consistent, i.e. some runs would work fine and some runs would throw the error. To get around this problem, I have put the SizedBox and the Align widgets inside the TableCell. This works fine for me. Just wanted to flag this up as well since the advice I found, and quoted in my question, that said wrap the TableCell in a SizedBox would lead to this problem.
Solution code snippet below:
TableCell(
child: SizedBox(
height: _height,
child: Align(
alignment: Alignment.centerLeft,
child: Text(_label),
),
),
);

Widget flickers and disappears when scrolling

I'm already losing sleep over this.
I'm trying to display a chart inside a ListView (for scrolling). For some reason the contents of the Card flickers when scrolling and randomly completely disappears (the Card itself stays visible though).
Any idea why would that happen?
(...) ListView (...)
children: [Row ( children: [buildChartBox()] )] (...)
Expanded buildChartBox() {
return Expanded(
child: Card(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
children: [
Column(
mainAxisSize: MainAxisSize.min,
children: [
chartTitles(
title: 'Items',
subtitle: 'by value'),
SizedBox(
height: 300,
child: ValuesChart(data: calculateValues(items)))
],
),
],
),
),
),
);
}
Row chartTitles({String title = '', String subtitle = ''}) {
return Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(title, style: text_charttitle),
Text(subtitle, style: text_chartsubtitle),
],
)
],
);
}
Things tried:
Both of these were originally Stateless Widgets; I changed to simple
methods to simplify but it didn't change the weird behaviour.
Replacing the chartTitles return with an empty Container (i.e. removing the titles) does mitigate the issue. The chart then stays displayed but also flickers slightly.
Replacing the ListView with a SingleChildScrollView doesn't change anything.
EDIT: Code for the ValuesChart:
import 'package:fl_chart/fl_chart.dart';
class ValuesChart extends StatelessWidget {
final Map<String, int> data;
const ValuesChart({required this.data});
#override
Widget build(BuildContext context) {
return Container(
child: PieChart(
_theData(data),
));
}
}
Note I'm using a package called 'fl_chart'. _theData just returns various parameters for the chart, I don't think it's relevant.
Try to replace ListView with SingleChildScrollView
ListViews in flutter by default using what it is called in Android RecyclerView to efficiently use render resources.
If you are interested here an article
https://medium.com/1mgofficial/how-recyclerview-works-internally-71290de5d2c4

Flutter two widgets in body?

So my flutter app works fine, however I want to make some changes to it.
The 1st change I want to do is create a horizontal scroll widget that has images that are clickable that change stations.
But to do this I first need to join two widgets into one.
In my home_widget.dart file I have this code:
final List<Widget> _children = [TracksWidget(),
NewsWidget(),
AboutWidget()];
The code above changes the body section of the home_widget.dart section. But now what I want to do is add StationsWidget() to the bottom of the TrackWidget() - Note this widget refreshes every 30 seconds. So ideally I need to make sure it does not refresh with it.
So can a body have two widgets?
Full code of test.dart (which is where I am trying to add the widget)
import 'package:flutter/material.dart';
import 'trackswidget.dart';
class TestWidget extends StatelessWidget {
// final Color color;
// PlaceholderWidget(this.color);
#override
Widget build(BuildContext context) {
double c_width = MediaQuery.of(context).size.width;
return new Container (
padding: const EdgeInsets.all(16.0),
margin: const EdgeInsets.only(bottom: 0.0),
width: c_width,
child: new SingleChildScrollView(
child: new Column (
children: <Widget>[
Column(
children: <Widget>[
Child: TracksWidget()
],
),
Column(
children: <Widget>[
new Text('Advertise on ',style: TextStyle(color: Colors.black, fontSize: 20.0),),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children:
),
],
),
]
),
),
);
}
}
The error I am getting is
'Widget' is from 'package:flutter/src/widgets/framework.dart' ('/C:/src/flutter/packages/flutter/lib/src/widgets/framework.dart').
Try changing the type of the parameter, or casting the argument to 'List'.
children: TracksWidget,
Just so you all know what I am trying to achieve. We redesigned our iOS Swift app and now want our flutter app to match.
Strictly speaking, no, the body takes exactly one Widget. You could say it takes none if you pass null, but you cannot pass more than one.
However, there are widgets that group other widgets. Your one body widget could as well be a Row and that row can have multiple child widgets.
You already did that in your title.
A nice graphical representation can be found in the documentation.

Making a 2x2 grid in Flutter

I'm trying to create a 2x2 grid for displaying some info in cards. Disclaimer: I'm totally new to Dart and Flutter, so expect a lot of ignorance on the topic here.
These cards should have a fixed size, have an image, display some text... and be positioned from left to right, from top to bottom.
First, I tried to use the Flex widget, but it seems to only work horizontally or vertically. Therefore, my only solution was to use two Flexes, but only showing the second when the amount of elements is higher than 2 (which would only use one row).
Then, I tried using GridView, but it doesn't work in any possible way. It doesn't matter which example from the Internet I copy and paste to begin testing: they just won't show up in the screen unless they're the only thing that is shown in the app, with no other widget whatsoever. I still don't understand why that happens.
This is my current code:
First widgets in "home_page.dart":
return Scaffold(
appBar: AppBar(
// Here we take the value from the MyHomePage object that was created by
// the App.build method, and use it to set our appbar title.
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Padding(padding: EdgeInsets.only(top: 30)),
Text(
'App test',
style: TextStyle(fontSize: 24),
),
EventsList(key: new Key('test')),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
), // This trailing comma makes auto-formatting nicer for build methods.
);
The "EventList" part is a widget that should represent the grid functionality I explained before. This class gets some info from a service (which currently just sends some hardcoded info from a Future), and paints the given widgets ("Card" items, basically) into the EventList view:
class _EventsListState extends State<EventsList> {
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Event>>(
future: new EventsService().getEventsForCoords(),
builder: (context, AsyncSnapshot<List<Event>> snapshot) {
if (snapshot.hasData) {
return Padding(
padding: EdgeInsets.only(left: 20, right: 20),
child: Flex(
direction: Axis.horizontal,
verticalDirection: VerticalDirection.down,
mainAxisAlignment: MainAxisAlignment.center,
children: generateProximityEventCards(snapshot.data),
));
} else {
return CircularProgressIndicator();
}
});
}
List<Card> generateProximityEventCards(List<Event> eventList) {
// Load Events from API
print(eventList);
// Render each card
return eventList.map((Event ev) {
return Card(
child: Padding(
padding: EdgeInsets.only(bottom: 15),
child: Column(
children: <Widget>[
Image(
fit: BoxFit.cover,
image: ev.imageUrl,
height: 100,
width: 150,
),
Padding(
child: Text(ev.name),
padding: EdgeInsets.only(left: 10, right: 10),
),
Padding(
child: Text(ev.address),
padding: EdgeInsets.only(left: 10, right: 10),
),
],
),
));
}).toList();
}
}
This is how it currently looks:
As I said before, I understand that the Flex widget can't really get that 2x2 grid look that I'm looking for, which would be something like this (done with Paint):
So, some questions:
How can I get a grid like that working? Have in mind that I want to have more stuff below that, so it cannot be an "infinite" grid, nor a full window grid.
Is it possible to perform some scrolling to the right in the container of that grid? So in case there are more than 4 elements, I can get to the other ones just scrolling with the finger to the right.
As you can see in the first image, the second example is bigger than the first. How to limit the Card's size?
Thank you a lot for your help!
The reason the gridview was not working is because you need to set the shrinkWrap property of theGridView to true, to make it take up as little space as possible. (by default, scrollable widgets like gridview and listview take up as much vertical space as possible, which gives you an error if you put that inside a column widget)
Try using the scrollable GridView.count widget like this and setting shrinkWrap to true:
...
GridView.count(
primary: false,
padding: /* You can add padding: */ You can add padding const EdgeInsets.all(20),
crossAxisCount: /* This makes it 2x2: */ 2,
shrinkWrap: true,
children: generateProximityEventCards(snapshot.data),
...
Is this what you exactly want?
do let me know so that I can update the code for you
import 'package:flutter/material.dart';
class List extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text('Inicio', style: TextStyle(color: Colors.black, fontSize: 18.0),),
),
body: GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
children: List.generate(
50,//this is the total number of cards
(index){
return Container(
child: Card(
color: Colors.blue,
),
);
}
),
),
);
}
}

Display a built list of widgets in Dart

I've got a list of widgets I've built with a for in loop within a function but I'm unsure as how to display all the widgets as children of a column.
The function displayAllCards() builds the list, each widget starts with padding, i don't know if that helps or not.
Here's a very stripped down version of my unworking code.
The function to build a widget:
Widget displayCard(String bitCurrency) {
return Padding(
padding: EdgeInsets.fromLTRB(18.0, 18.0, 18.0, 0),
child: Text("$bitCurrency"),
);
}
And then I built another function to create a list of widgets throwing in different bitCurrencies:
List displayAllCards() {
List<Widget> cards = [];
for (String bitCurrency in cryptoList) {
cards.add(displayCard(bitCurrency));
}
return cards;
}
And finally the output with the Flutter is where I'm dying.
body: Column(
children: <Widget>[
displayAllCards(),
],
),
I kinda know why it's not working, but I'm unsure how to make it correct.
I'm fairly new to Dart so please be gentle.
Just use spread operator (...)
body: Column(
children: <Widget>[
...displayAllCards(),
],
),