How to update a custom Stateful Widget using Floating Action Button - flutter

I've recently started using Flutter just for fun, and I'm stuck on adding actual functionality to the code without having everything inside one class.
Essentially, I'm trying to use a FloatingActionButton to increment the value of a Text Widget which stores the value of the user's level as an integer, but I don't want to have the whole app as a StatefulWidget because only the level is going to be updated. When the button is pressed, the value should increment by 1 and then show the new value on the screen.
I have the Level Text Widget inside a StatefulWidget class along with a function to update the level by one and set the state; the MaterialApp inside a StatelessWidget class; and the main body code inside another StatelessWidget class.
If this isn't the best way to do it please do let me know so I can improve for future projects, thanks.
main.dart
import 'package:flutter/material.dart';
main() => runApp(Start());
/// The Material App
class Start extends StatelessWidget{
#override
Widget build(BuildContext context){
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
backgroundColor: Colors.grey[800],
appBar: AppBar(
title: Text("Home Page"),
backgroundColor: Colors.cyan,
centerTitle: true,
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.orange,
child: Icon(Icons.add, color: Colors.black,),
),
body: HomePage(),
),
);
}
}
/// Main Content for the page (body)
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// removed other children so there's less code to scan through for you :)
Padding(
padding: EdgeInsets.fromLTRB(30, 0, 0, 0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
// Text that just says "Level"
Text(
"Level",
style: TextStyle(
color: Colors.orange,
fontWeight: FontWeight.bold,
fontSize: 32,
),
),
// space between text and actual level value
SizedBox(height: 10),
// Create new level widget
Level(),
],
),
),
],
),
);
}
}
/// Updating level using a Stateful Widget
class Level extends StatefulWidget{
#override
State<StatefulWidget> createState(){
return _LevelState();
}
}
class _LevelState extends State<Level>{
int level = 0;
void incrementLevel(){
setState(() {
level += 1;
});
}
#override
Widget build(BuildContext context){
return Text(
"$level",
style: TextStyle(
color: Colors.grey[900],
fontWeight: FontWeight.normal,
fontSize: 28,
),
);
}
}

It actually is a weird way of doing it. However, there is various ways of achieving this
To give an example:
You can use KEYs to remotely redraw the child state
If you want an advanced solution that can assist you in bigger projects. You can use state management tecniques. You can find a lot of tutorials in the internet but these are some of them. BLOC, Provider, InheritedWidget.
Basicaly all of them does the same thing. Lifts up the state data so the place of the redrawn widget on the widget tree will not be important.
I strongly encourage you to watch some tutorials starting with the Provider. I hope this helps

Related

Complete Dialog is not scrollable but just the listview inside it is scrollable

I am trying to create a modal which contains some details and is scrollable. The modal contains a ListView which is scrollable, but the complete modal is not.
I have tried adding the following options to the ListView, but it didn't help
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics()
The minimum reproducible code is
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
// Application name
title: 'Flutter Stateful Clicker Counter',
theme: ThemeData(
// Application theme data, you can set the colors for the application as
// you want
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Clicker Counter Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({Key? key, required this.title}) : super(key: key);
// This widget is the home page of your application. It is stateful, meaning
// that it has a State object (defined below) that contains fields that affect
// how it looks.
// This class is the configuration for the state. It holds the values (in this
// case the title) provided by the parent (in this case the App widget) and
// used by the build method of the State. Fields in a Widget subclass are
// always marked "final".
#override
_MyHomePageState createState() => _MyHomePageState();
}
_buildTheDescriptionWizard() {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
child: Text(
"Paneer is a fresh cheese used in a wide variety of Indian recipes, made by heating and then curdling milk using acid. It's very mild and milky in flavor, white in color, and its texture is soft, spongy, and squeaky. This texture helps it to absorb the flavors of sauces or marinades. It can be made from cow's milk or buffalo milk, either pasteurized or raw, and can be made from whole, skim or reduced-fat milk. ",
textAlign: TextAlign.left,
style: TextStyle(fontSize: 15, height: 1.5, color: Colors.black))),
);
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
// This call to setState tells the Flutter framework that something has
// changed in this State, which causes it to rerun the build method below
// so that the display can reflect the updated values. If we changed
// _counter without calling setState(), then the build method would not be
// called again, and so nothing would appear to happen.
_counter++;
});
}
#override
Widget build(BuildContext context) {
// This method is rerun every time setState is called, for instance as done
// by the _incrementCounter method above.
//
// The Flutter framework has been optimized to make rerunning build methods
// fast, so that you can just rebuild anything that needs updating rather
// than having to individually change instances of widgets.
return Dialog(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)),
elevation: 16,
child: createModal());
}
createModal() {
return Container(
height: 600,
width: 800,
child: Container(
color: Colors.white,
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(children: <Widget>[
Container(
height: 300,
child: ListView(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
children: [
Container(
width: 300,
color: Colors.white,
// child:
// DropShadow(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Image.network(
'https://picsum.photos/250?image=9',
height: 250,
width: 250),
),
// )
),
Container(
width: 450, height: 300, color: Colors.black)
],
)),
Divider(),
_buildTheDescriptionWizard()
]))));
}
}
Can someone please help me figure out how I can make the complete modal scrollable ?
remove SingleChildScrollView and Column just use ListView and don't use shrinkWrap: true and physics: const NeverScrollableScrollPhysics()

Structure of a flutter project

How to structure a flutter project like this one:
Example restaurant pos image
Do you find this beginning of the tree structure correct:
class HomePageState extends State<HomePage>{
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Row(
children: [
Container( // menu
width:60,
color: Colors.white,
),
Expanded( // body
child: Container(
color: Colors.red,
),
),
Container( // ListProducts
width:300,
color: Colors.green,
),
],
),
backgroundColor: Color.fromARGB(255 , 244 , 246, 250),
)
);
}
}
code preview
You might want to place that MaterialApp into a separate parent widget (I think it will cause issues when using MediaQuery and Theme inside the same build method). It also might be cleaner down the line to extract every part (menu, body, ListProducts) into separate widgets.
Also, I would advise you to take a look at the LayoutBuilder widget,
and the ressources on this page if the app is meant to work on narrower screens.
Oh and if you don't know about state management, definitely check this out.

Flutter - Custom Timer avoid moving numbers?

I'm struggling to fix the following issue shown in the gif. I'm using this package for the timer. And I can't figure it out how to avoid the moving of the countdown timer while counting down. It’s moving because of different widths of each number.
Gif:via GIPHY
Code:
Consumer<RunSettingsModel>(
builder: (context, settings, _) => CustomTimer(
from: Duration(seconds: settings.runDuration),
to: Duration(seconds: 0),
controller: _runController,
builder: (CustomTimerRemainingTime remaining) {
final double percent = 1 -
remaining.duration.inSeconds.toDouble() /
settings.runDuration;
settings.remainingTime = remaining.duration.inSeconds;
return Column(
children: [
Container(
child: Text(
"${remaining.hours}:${remaining.minutes}:${remaining.seconds}",
style: Theme.of(context).textTheme.headline3,
),
),
you can import 'dart:ui';
and then in your Text Widget use a TextStyle of
fontFeatures: [FontFeature.tabularFigures()],
like so:
Text("${remaining.hours}:${remaining.minutes}:${remaining.seconds}",
style: TextStyle(fontSize: 30.0, fontFeatures: [FontFeature.tabularFigures()]),
);
Try to remove the Container wrapping your Text as shown in the custom_timer package.
As you can see, in the package example there is no Container, the Container might be changing it's size because it doesn't have a fixed width and height.
This is the package simple example:
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
appBar: AppBar(
title: Text("CustomTimer example"),
),
body: Center(
child: CustomTimer(
from: Duration(hours: 12),
to: Duration(hours: 0),
onBuildAction: CustomTimerAction.auto_start,
builder: (CustomTimerRemainingTime remaining) {
return Text(
"${remaining.hours}:${remaining.minutes}:${remaining.seconds}",
style: TextStyle(fontSize: 30.0),
);
},
),
),
),
);
}
You should try to separate each digit(hours, minutes and seconds) in its own Text widget and wrap them with a Container to keep them always at the center. I am creating a new widget for the digits because most of the widgets used would repeat themselves.
class DigitsContainer extends StatelessWidget {
const DigitsContainer(
this.text, {
required this.style,
});
final String text;
final TextStyle style;
#override
Widget build(BuildContext context) {
return Expanded(
child: Container(
child: Center(
child: Text("", style: style),
),
),
);
}
}
Then in your main widget.
...
return Column(children: [
DigitsContainer(remaining.hours.toString(), style: style),
Text(":", style: style),
DigitsContainer(remaining.minutes.toString(), style: style),
Text(":", style: style),
DigitsContainer(remaining.minutes.toString(), style: style),
]),
...
Also make sure to wrap this Column in a Container with fixed with so that you dont have problem with the Expanded widget inside the DigitsContainer one. I hope this works.

Flutter: Very basic stupid beginner question

I am trying to learn Flutter and so I am following a YouTube tutorial.
Now I have a question I can't really describe, it is working but i don't understand why.
My Program contains two .dart files, you can see them below.
Is the the Question Class in questions.dart executed by pressing Button 1 or 3 (Answer 1, Answer 3)?
If I understand that correctly the buttons will execute "onPressed: _answerQuestions,". But _answerQuestions only changes the State of "questionIndex" and is not calling something else, right? The UI gets rebuild and the Question Text changes.
But Questions Class does change the appearence of the text with the "style", this is its only task right now?
So why do we have a second dart File for that? We could simply change the style within the main.dart?
main.dart:
import 'package:flutter/material.dart';
import './question.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
var questionIndex = 0;
void _answerQuestions() {
setState(() {
questionIndex = questionIndex + 1;
});
print(questionIndex);
}
#override
Widget build(BuildContext context) {
var questions = [
'What\'s your favorite color?',
'What\'s your favorite animal?',
];
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Column(
children: [
Question(questions[questionIndex]),
RaisedButton(
child: Text('Answer 1'),
onPressed: _answerQuestions,
),
RaisedButton(
child: Text('Answer 2'),
onPressed: () => print('Answer 2 chosen')),
RaisedButton(
child: Text('Answer 3'),
onPressed: _answerQuestions,
),
],
),
),
);
}
}
And question.dart:
import 'package:flutter/material.dart';
class Question extends StatelessWidget {
final String questionText;
Question(this.questionText);
#override
Widget build(BuildContext context) {
return Text(
questionText,
style: TextStyle(fontSize: 28),
);
}
}
Is the the Question Class in questions.dart executed by pressing
Button 1 or 3 (Answer 1, Answer 3)?
Both. Question is a StatlessWidget holding your question.
When you press any of the buttons(1 & 2) MyAppState gets rebuilt with a new question index and because you pass the question text to the Question Widget using that index, Question(questions[questionIndex]), Question will get a new value and show it, because you called setState, if you had not, behind the scenes it would still change the value but because the widget wouldn't be rebuilt, the visual side would stay the same.
The why you have two separate files? It makes it easier to see what your app is doing, especially for bigger projects. Usually, one file should have one purpose, do not mix functionalities in one file or you'll get a messy code really fast.
In this case, the equivalent for not having the question in a separate file would be:
body: Column(
children: [
// from
// Question(questions[questionIndex]),
// to
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
RaisedButton(
child: Text('Answer 1'),
onPressed: _answerQuestions,
),
RaisedButton(
child: Text('Answer 2'),
onPressed: () => print('Answer 2 chosen')),
RaisedButton(
child: Text('Answer 3'),
onPressed: _answerQuestions,
),
],
),
But what if you need to use that same question in several places?
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
You'll have to write that same code every time you need it, and this is a really simple one, imagine you have a widget with much more code, do you really want to write it all over again every time you need it?
No, you just create a new widget thas has the functionality you need and call it whenever you need it.
Question(questions[questionIndex]),
Question(questions[questionIndex]),
Question(questions[questionIndex]),
Question(questions[questionIndex]),
When i code in flutter, i usually tend to start a file for every new "screen" in the app.
Also, i create one file for I/O (api connections, DB...), and a file for common functions.
It is good to have several files because the moment you want to build a big app you will realise how long it is. My last app was around 10k lines of code. Impossible to mantain with only 1 file.
Also, dividing by file is useful for code reutilisation.
GL
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
var questionIndex = 0;
void _answerQuestions() {
setState(() {
questionIndex = questionIndex + 1;
});
print(questionIndex);
}
#override
Widget build(BuildContext context) {
var questions = [
'What\'s your favorite color?',
'What\'s your favorite animal?',
];
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('My First App'),
),
body: Column(
children: [
Text(
questions[questionIndex],
style: TextStyle(fontSize: 28),
),
RaisedButton(
child: Text('Answer 1'),
onPressed: _answerQuestions,
),
RaisedButton(
child: Text('Answer 2'),
onPressed: () => print('Answer 2 chosen')),
RaisedButton(
child: Text('Answer 3'),
onPressed: _answerQuestions,
),
],
),
),
);
}
}
It can be done.. As #Michael Said you'll get a messy code really fast. You should follow BEST PRACTICES While Learning a Language. It is equally IMPORTANT. Will help you in long RUN.

Flutter Dynamic maxHeight

I build a scrolling textfield like proposed in https://github.com/flutter/flutter/issues/9365. Now I want the maxHeight of the ConstrainedBox change dynamically, according to the shown or not shown keyboard. Is there a way to accomplish this?
Widget _buildTextInput() {
return new Container(
padding: new EdgeInsets.all(7.0),
child: new ConstrainedBox(
constraints: new BoxConstraints(
maxHeight: 150.0 <-- This should be dynamic
),
child: new SingleChildScrollView(
scrollDirection: Axis.vertical,
reverse: true,
// here's the actual text box
child: new TextField(
keyboardType: TextInputType.multiline,
maxLines: null, //grow automatically
decoration: new InputDecoration.collapsed(
hintText: 'Please enter a lot of text',
),
),
),
),
);
}
The red box should be the constrained box with open keyboard.
And like so with a closed keyboard.
EDIT:
I'm trying to build an input field that's kind of like posting on Twitter. I need to combine a CircleAvatar, a TextField and a GridView to display the user's avatar, his post and a few images. Like on Twitter, I want the whole thing to scroll, not only the TextField - both while typing and while reviewing what the user typed or uploaded. Besides, the (multiline) TextField should scroll while typing in the visible area (keeping the open or closed keyboard in mind), so the user can see what he's typing.
Even though the Flutter TextField autoscrolls now, I can't get this whole cluster working. Any idea?
Autoscroll has been supported natively in the textfield widget since I believe the beginning of June (2018) - I think this is the commit that added it. You may need to update to the most recent flutter build for it to work, but that is included in version 5.5.
This simplifies matters a bit - this should be all you need to do to get it to work as you want:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Example"),
),
body: new Container(
padding: new EdgeInsets.all(7.0),
child: new TextField(
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: new InputDecoration.collapsed(
hintText: 'Please enter a lot of text',
),
),
),
),
);
}
}
EDIT: To answer the OP's edited question - wanting to have other elements within the same scrolling pane as the textview, I've made this:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Example"),
),
body: new SingleChildScrollView(
child: new Container(
padding: new EdgeInsets.all(7.0),
child: new Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
new CircleAvatar(
backgroundColor: Colors.blue,
child: new Text("AB"),
),
new Expanded(
child: new Column(
children: [
new TextField(
keyboardType: TextInputType.multiline,
maxLines: null,
decoration: new InputDecoration.collapsed(
hintText: 'Please enter a lot of text',
),
),
new Container(
height: 300.0,
width: 100.0,
color: Colors.green,
),
],
),
),
],
),
),
),
),
);
}
}
By using a SingleChildScrollView, we're still allowing the children to set the size of the viewport (as opposed to a MultiChildLayoutDelegate etc which has to have that size set). The textview grows as big as it needs to be, but doesn't scroll itself as its height is not constrained. The Expanded is needed within the row to make sure that the right side (with the text & pictures) is as large as it can be horizontally.