Flutter widgets best practices: Inner Class vs Function - flutter

I am a Java developer and currently learning about Flutter/Dart. I am an adept of clean code with small functions and some widget examples just scare the s*** out of me.
I am trying to implement a Card widget with some transaction information (price, title and date). Currently the code looks like this:
class TransactionCard extends StatelessWidget {
final Transaction _transaction;
TransactionCard(this._transaction);
#override
Widget build(BuildContext context) {
return Container(
child: Card(
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_PriceContainer(_transaction.amount),
_DetailContainer(_transaction.title, _transaction.date),
],
),
),
);
}
}
// Inner Widgets
class _PriceContainer extends Container {
_PriceContainer(double amount)
: super(
margin: EdgeInsets.symmetric(
vertical: 10,
horizontal: 15,
),
decoration: BoxDecoration(
border: Border.all(
color: Colors.purple,
width: 2,
),
),
padding: EdgeInsets.all(10),
child: Text(
amount.toString(),
style: _amountTextStyle(),
),
);
}
class _DetailContainer extends Container {
_DetailContainer(String title, DateTime date)
: super(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
title,
style: _titleTextStyle(),
),
Text(
date.toString(),
style: _dateTextStyle(),
),
],
),
);
}
// Text styles
TextStyle _amountTextStyle() {
return TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.purple,
);
}
TextStyle _titleTextStyle() {
return TextStyle(
fontWeight: FontWeight.bold,
fontSize: 18,
);
}
TextStyle _dateTextStyle() {
return TextStyle(color: Colors.grey);
}
I have used two approaches:
For the inner widgets I extended Containers and gave then specific styling.
For the text styles I created a function returning the desired style.
Is there an approach preferable to the other? A third one? Is there a bad practice to create multiple Widgets on the same file?

Composition > inheritance
As mentioned in the comments and in the Flutter documentation, you should always compose widgets instead of inheriting from e.g. a Container.
In your case, this would look like this:
class _PriceContainer extends StatelessWidget {
final double amount;
const _PriceContainer({
Key key,
this.amount,
}) : super(key: key);
#override
Widget build(BuildContext context) => Container(
margin: const EdgeInsets.symmetric(
vertical: 10,
horizontal: 15,
),
decoration: BoxDecoration(
border: Border.all(
color: Colors.purple,
width: 2,
),
),
padding: EdgeInsets.all(10),
child: Text(
amount.toString(),
style: _amountTextStyle,
),
);
}
This is analogous for your other widgets.
Top-level functions
Declaring top-level functions is generally fine, however, in this case, you should really define a top-level property instead - preferably declare a const to take advantage of compile-time constants:
const _amountTextStyle = TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
color: Colors.purple,
);
You should be able to apply the same to your other text styles.

Related

How to set container width conditionally in flutter?

I have a component having a container with no width initially. What I want is when I specify the full-width property to true, it takes double.infinity as the width otherwise it takes no width at all. This is my component:
import 'package:flutter/material.dart';
class InfoColumn extends StatelessWidget {
const InfoColumn({
Key? key,
required this.heading,
required this.text,
this.fullWidth = false,
}) : super(key: key);
final String heading;
final String text;
final bool fullWidth;
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
heading,
style: const TextStyle(
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
const SizedBox(
height: 10,
),
Container(
// TODO: SET WIDTH HERE
// width: fullWidth ? double.infinity : ,
padding: const EdgeInsets.symmetric(
horizontal: 30,
vertical: 12,
),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10),
),
child: Text(
text,
style: const TextStyle(
fontSize: 12,
),
),
),
],
);
}
}
How can I achieve this?
Container width is nullable, and containers will only take up as much space as their children require.
A simple ternary statement will achieve what you're after:
Container(
width: fullWidth ? double.infinity : null,
// ...
),

SingleChildScrollView still causes the screen to overflow

I have a simple screen built using the code shown below. I want to keep the ad banner at the top at all times while the Container() below it to be scrollable. This is the reason I put SingleChildScrollView() in the lower container.
But it still overflows the screen with the following error:
════════ Exception caught by rendering library ═════════════════════════════════════════════════════
The following assertion was thrown during layout:
A RenderFlex overflowed by 162 pixels on the bottom.
This is what the screen looks like:
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
// colorFilter: ColorFilter.mode(Colors.white, BlendMode.color),
image: AssetImage("assets/feathers_bg.jpg"),
fit: BoxFit.cover,
),
),
child: Column(
children: [
AdmobBanner(
//below is test id
adUnitId: 'ca-app-pub-3940256099942544/6300978111',
adSize: AdmobBannerSize.FULL_BANNER,
),
Padding(
padding: const EdgeInsets.all(40.0),
child: SingleChildScrollView(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
padding: EdgeInsets.all(10),
child: Column(
children: [
Image.network(_birdDetails.thumbnail.source),
SizedBox(height: 10,),
Container(
child: Column(
children: [
Text(
_birdName,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.teal,
fontWeight: FontWeight.bold,
),
),
Text(
_birdDetails.description,
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontStyle: FontStyle.italic,
),
),
],
),
),
SizedBox(height: 20,),
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Text(
_birdDetails.extract,
textAlign: TextAlign.justify,
style: TextStyle(
fontSize: 16
),
),
),
SizedBox(height: 10,),
],
),
),
),
),
],
),
),
TL;DR version. Your SingleChildScrollView needs to be Expanded (you can put Expanded -> Padding - > SingleChildScrollView).
Longer version you can read in the official documentation, this section describes a similar scenario:
One common reason for this to happen is that the Column has been
placed in another Column (without using Expanded or Flexible around
the inner nested Column). When a Column lays out its non-flex children
(those that have neither Expanded or Flexible around them), it gives
them unbounded constraints so that they can determine their own
dimensions (passing unbounded constraints usually signals to the child
that it should shrink-wrap its contents). The solution in this case is
typically to just wrap the inner column in an Expanded to indicate
that it should take the remaining space of the outer column, rather
than being allowed to take any amount of room it desires.
And here is a bit simplified version of your code that is easily reproducible (to paste and run for example in dartpad):
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'SO Question : 64200763'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
StringBuffer birdDetails;
var rng = new Random();
#override
initState(){
super.initState();
birdDetails = new StringBuffer();
for(int i=0; i<4000; i++){
birdDetails.write(String.fromCharCode(rng.nextInt(25) + 97));
if(rng.nextBool() && rng.nextBool()) birdDetails.write(' ');
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Container(
decoration: BoxDecoration(
color: Colors.green[400],
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Column(
children: [
Container(
height: 100,
width: double.maxFinite,
color: Colors.yellow,
child: Text('Ad placeholder', textAlign: TextAlign.center)
),
Expanded(
child: Padding( // Here is your fix, place expanded above the SingleChildScrollView
padding: const EdgeInsets.all(40.0),
child: SingleChildScrollView(
child: Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
padding: EdgeInsets.all(10),
child: Column(
children: [
Image.network('https://picsum.photos/id/1024/512/288'),
SizedBox(height: 10,),
Container(
child: Column(
children: [
Text(
'Bird name',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 20,
color: Colors.teal,
fontWeight: FontWeight.bold,
),
),
Text(
'Bird (random) description',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14,
fontStyle: FontStyle.italic,
),
),
],
),
),
SizedBox(height: 20,),
Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Colors.grey.shade300,
borderRadius: BorderRadius.all(Radius.circular(20)),
),
child: Text(
birdDetails.toString(),
textAlign: TextAlign.justify,
style: TextStyle(
fontSize: 16
),
),
),
SizedBox(height: 10,),
],
),
),
),
),
),
],
),
),
);
}
}
End result (as per how you organized the widgets, but without overflows):
PS, before posting a question I highly recommend stripping / replacing the code of all dependencies that some users might or might not have at hand (like AdMob), unnecessary assets (like AssetImage) and lastly class structures that aren't defined in the question (like birdDetails.thumbnail.source). It might help you debug the problem on your own and if it doesn't it makes it easier for people that are trying to help you ;).

What is the easy way to create reusable widgets in flutter?

Container(
child: Column(
children: <Widget>[
Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.only(left: 10.0),
child: Text("Random Text",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.black)),
),
Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.all(10.0),
child: Text("Owner",
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.grey)),
),
],
),
),
I don't know if it's an easy way. But for a simple reusable widget, you can place your widget inside a StatelessWidget or a StatefulWidget.
Here's the example:
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Column(
children: <Widget>[
MyReusableWidget('Nikola Tesla', 'Owner'), //Input the name and role variable when you call the widget
MyReusableWidget('Albert Einstein', 'Developer'),
MyReusableWidget('Isaac Newton', 'Technician'),
],
),
),
);
}
}
class MyReusableWidget extends StatelessWidget {
final String name; // provide a place for the input's data
final String role;
MyReusableWidget(this.name, this.role);
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.only(left: 10.0),
child: Text(
name, // This is where you place your 'name' data
style: TextStyle(
fontWeight: FontWeight.bold, color: Colors.black),
),
),
Container(
alignment: Alignment.topLeft,
padding: EdgeInsets.all(10.0),
child: Text(
role, // This is where you place your 'role' data
style: TextStyle(
fontWeight: FontWeight.bold, color: Colors.grey),
),
),
],
),
);
}
}
I'm creating a widget called MyReusableWidget. I am gonna call that widget inside my MyApp 3 times. And then each widget should provide different names and roles.
So inside my MyReusableWidget, I provide two String data-types called name and role to store my data when I call the widget.
final String name; // provide a place for the input's data
final String role;
MyReusableWidget(this.name, this.role);
And then I want to place my name and role variable inside a Text widget:
child: Text(
name, // This is where you place your 'name' data
style: TextStyle(
fontWeight: FontWeight.bold, color: Colors.black),
),
and:
child: Text(
role, // This is where you place your 'role' data
style: TextStyle(
fontWeight: FontWeight.bold, color: Colors.grey),
),
After that, inside my MyApp widget, I can call MyReusableWidget as much as I want and provide different name and role value on each widget.
Column(
children: <Widget>[
MyReusableWidget('Nikola Tesla', 'Owner'), //Input the name and role variable when you call the widget
MyReusableWidget('Albert Einstein', 'Developer'),
MyReusableWidget('Isaac Newton', 'Technician'),
],
),
Result:
And that's it.
You can store any kind of data-type on it (String, int, double, etc).
I hope it will be helpful.

Expand widget to match parent height inside the row in flutter

I want to create below UI
How to draw a line between widget, If try to expand the column line inside the row it gives error of infinity height
//Below some code not able to post all the code.
ListView(
shrinkWrap: true,
children: <Widget>[
new _Row(
label: "1",
body:
"Screening: We will screen your current portfolio to check your Portfolio's health.",
),
SizedBox(
height: 16,
),
],
),
class _Row extends StatelessWidget {
final String label;
final String body;
const _Row({Key key, this.label, this.body});
#override
Widget build(BuildContext context) {
return Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
width: 28,
height: 28,
decoration: BoxDecoration(
color: Color(0xff76587b).withOpacity(0.5),
shape: BoxShape.circle,
),
child: Center(
child: Text(
label,
textAlign: TextAlign.start,
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.w500),
),
),
),
SizedBox(
width: 16,
),
Flexible(
child: Text(
body,
style: kModelTargetCARGTInactiveTextLabel.copyWith(
color: Colors.black, height: 1.4),
),
),
],
);
}
}
In this code line between bullet points not visible. I tried may option but it not working.

Disable Flutter text Baseline

How can I disable the textbaseline?
because my text is not centered
I try to center a text in a contair. I use this font: https://www.dafont.com/young.font?l[]=10&l[]=1
import 'package:flutte_template/styles/theme_dimens.dart';
import 'package:flutter/material.dart';
class RoundedChip extends StatelessWidget {
final Widget child;
final Color color;
const RoundedChip({#required this.child, #required this.color});
#override
Widget build(BuildContext context) {
return Container(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: ThemeDimens.padding4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsets.symmetric(vertical: ThemeDimens.padding4),
child: Text('Drama', style: ThemeTextStyles.mediaDetailGenreText),
),
],
),
),
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(999),
),
color: color,
),
);
}
}
static const TextStyle mediaDetailGenreText = TextStyle(color: ThemeColors.textColor, fontSize: 15, fontWeight: FontWeight.w500);
I hope it helps after this long time :)
wrap the text widget with baseline widget
Baseline(
baselineType: TextBaseline.alphabetic,
child: Text(
'Some Text',
textAlign: TextAlign.center,
),
baseline: 40.0,
),
Just add an inputDecoration with no input border to the textField like this:
TextField(
decoration: InputDecoration(border: InputBorder.none),
)
Try TextDecoration.none, its work for me.
ex: Text("Hello", style: TextStyle(fontSize: 12, decoration: TextDecoration.none),)
I think the issue is on the line height of the Font...
You can tweak that value directly, is in percentage terms of the font size.
So, try the values by eye:
lets say:
Text( "Drama", height: 0.8 );