I'm pretty new to Flutter, and I wanted to create a folder effect with tabs for my portfolio website where you click on the tabs to change what displayed in the center of the folder. Right now the buttons are dynamically generated as children of a row widget. The problem is that at smaller screen widths the text is either too small or gets cut off altogether. I even tried to figure out how to do a sort of multi-line row but gave up.
What I would ideally like is that the buttons wrap such that any buttons that would make the parent too long are placed on a separate row. However, I'm open to any solution that allows all the tabs to fit on screen without the text in the buttons being super shrunk.
My current solutions as you can see is to just scale the text down at smaller screen widths so that its all at least visible. I'm guessing that I'm either going to have redesign the way I implement the effect, or there's some relatively simple solution that I'm not aware of. I also tried replacing the Row widget that wraps the FolderButtons to a Wrap with no other modifications and that just caused the text to disappear.
Note: The way its currently implemented assumes and depends upon the folder being a perfect square.
Below are Folder, FolderButton, and ScaleSize classes respectively:
class Folder extends StatefulWidget {
const Folder({super.key});
static Column? getStaticPages(String page, BuildContext context) {
Map<String, Column> pages = {
"Welcome": Column(
children: [
Text(
'Welcome',
style: TextStyle(color: Colors.deepPurpleAccent.shade100),
)
],
),
"Web Dev's Handbook": Column(
children: [
Text(
"Web Dev's Handbook",
style: TextStyle(color: Colors.deepPurpleAccent.shade100),
),
TextButton(
onPressed: () => {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const Contents()))
},
child: const Text("Go"))
],
),
"Interactive Resume": Column(
children: [
Text(
'Interactive Resume',
style: TextStyle(color: Colors.deepPurpleAccent.shade100),
)
],
),
"Settings": Column(
children: [
Text(
'Settings',
style: TextStyle(color: Colors.deepPurpleAccent.shade100),
)
],
),
"Credits": Column(
children: [
Text(
'Credits',
style: TextStyle(color: Colors.deepPurpleAccent.shade100),
)
],
),
};
return pages[page];
}
static List<Map<String, dynamic>> staticTabs = [
{"title": "Welcome"},
{"title": "Web Dev's Handbook"},
{"title": "Interactive Resume"},
{"title": "Settings"},
{"title": "Credits"},
];
static List<FolderButton> generateTabs(int selectedTab, Function setTab) {
List<FolderButton> newTabs = [];
for (int x = 0; x < staticTabs.length; x++) {
bool selected;
if (selectedTab == x) {
selected = true;
} else {
selected = false;
}
newTabs.add(FolderButton(
title: staticTabs[x]["title"],
count: x,
selected: selected,
setTab: setTab));
}
return newTabs;
}
#override
State<Folder> createState() => _FolderState();
}
class _FolderState extends State<Folder> {
int _selectedTab = 0;
void _setTab(int count) {
setState(() {
_selectedTab = count;
});
}
#override
Widget build(BuildContext context) {
return Center(
child: Padding(
padding: const EdgeInsets.only(left: 1, right: 1, top: 20),
child: SizedBox(
height: 750,
width: 750,
child: Column(
children: [
Flexible(
flex: 1,
child: Padding(
padding: const EdgeInsets.only(left: 7.0),
child: Row(
children: Folder.generateTabs(_selectedTab, _setTab),
),
),
),
Flexible(
flex: 15,
fit: FlexFit.tight,
child: Container(
decoration: BoxDecoration(
color: Colors.deepPurpleAccent,
borderRadius: BorderRadius.circular(5)),
child: Padding(
padding: const EdgeInsets.all(5.0),
child: Container(
width: 800,
decoration:
const BoxDecoration(color: Colors.deepPurple),
child: Folder.getStaticPages(
Folder.staticTabs[_selectedTab]["title"], context)),
),
),
),
],
),
),
),
);
}
}
class FolderButton extends StatefulWidget {
const FolderButton(
{super.key,
required this.title,
required this.count,
this.selected = false,
required this.setTab});
final String title;
final int count;
final bool selected;
final Function setTab;
static final theme = <String, dynamic>{
"button": <String, dynamic>{
"picked": <bool, ButtonStyle>{
true: TextButton.styleFrom(
backgroundColor: Colors.deepPurple,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(5), topRight: Radius.circular(5)),
side: BorderSide(
color: Colors.deepPurpleAccent,
strokeAlign: StrokeAlign.outside))),
false: TextButton.styleFrom(
backgroundColor: Colors.deepPurple,
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(5), topRight: Radius.circular(5)),
side: BorderSide(
color: Colors.deepPurple, strokeAlign: StrokeAlign.outside)),
),
}
},
// TODO Make it so I don't need to do it like this
"padding": <bool, dynamic>{
true: const EdgeInsets.only(top: 3, left: 3, right: 3),
false: const EdgeInsets.only(top: 3, left: 3, right: 3)
}
};
static Color? getTabShading(selected) {
if (selected) {
return Colors.deepPurpleAccent;
}
return Colors.deepPurple;
}
static EdgeInsetsGeometry getTabPadding(selected) {
return theme["padding"][selected];
}
#override
State<FolderButton> createState() => _FolderButtonState();
}
class _FolderButtonState extends State<FolderButton> {
void changeSelected() {}
#override
Widget build(BuildContext context) {
return Flexible(
child: Container(
height: 100,
// Button Container
decoration: BoxDecoration(
// Container Decorations
color: FolderButton.getTabShading(widget.selected),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(5),
topRight: Radius.circular(5),
)),
// Button Padding
child: Padding(
padding: FolderButton.getTabPadding(widget.selected),
// Button
child: TextButton(
onPressed: () {
widget.setTab(widget.count);
},
// Style of button itself
style: FolderButton.theme["button"]?["picked"][widget.selected],
child: Text(
textAlign: TextAlign.center,
textScaleFactor: ScaleSize.textScaleFactor(context,
maxTextScaleFactor: 1.5),
// Text of the button
widget.title,
style: TextStyle(
color: Colors.deepPurpleAccent.shade100,
fontSize: 10,
height: 1))),
),
),
);
}
}
class ScaleSize {
static double textScaleFactor(BuildContext context,
{double maxTextScaleFactor = 2}) {
final width = MediaQuery.of(context).size.width;
double val = (width / 1400) * maxTextScaleFactor;
return max(1, min(val, maxTextScaleFactor));
}
}
Any general flutter tips, tricks, conventions, and/or all-around good ideas are appreciated.
Perhaps Wrap widget is what you need. It works just like a Row until you reach the limit of the horizontal size, and then places the next widget in a new "Row" below the previous.
Try using Expanded as a child for Row/Column or whatever you need there. I'm giving you a brief description from the flutter page. I think that for text that does not have a specific width it will be good.
https://api.flutter.dev/flutter/widgets/Expanded-class.html
The answer is simply to rebuild the component with Wrap as the parent to the Buttons as it wrap overflowing children below the rest. I will also be sure to maintain a bit more forethought in terms of responsive layout design while making use of the responsive framework package.
Related
I wrote the code to get data from List to chips and when click chips the colour changed to blue. But I want to fetch data from firestore instead "words list".
Instead this words list ...
Database collection image
I want to display "WordName" field in the chips.
My code..
class uitry extends StatefulWidget {
const uitry({Key? key}) : super(key: key);
#override
State<uitry> createState() => _uitryState();
}
class _uitryState extends State<uitry> {
List<String> wordList = [
'Shopping',
'Brunch',
'Music',
'Road Trips',
'Tea',
'Trivia',
'Comedy',
'Clubbing',
'Drinking',
'Wine',
];
List<String> selectedWord = [];
List<String>? deSelectedWord = [];
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
return Scaffold(
body: Container(
decoration: const BoxDecoration(
image: DecorationImage(
image: AssetImage(Config.app_background4), fit: BoxFit.fill),
),
child: SafeArea(
child: Center(
child: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.only(top: 14, right: 0),
child: Column(
children: [
SizedBox(
width: width * 0.94,
height: height * 0.30,
child: Column(
children: <Widget>[
const SizedBox(height: 16),
Wrap(
children: wordList.map(
(word) {
bool isSelected = false;
if (selectedWord!.contains(word)) {
isSelected = true;
}
return GestureDetector(
onTap: () {
if (!selectedWord!.contains(word)) {
if (selectedWord!.length < 50) {
selectedWord!.add(word);
deSelectedWord!.removeWhere(
(element) => element == word);
setState(() {});
print(selectedWord);
}
} else {
selectedWord!.removeWhere(
(element) => element == word);
deSelectedWord!.add(word);
setState(() {
// selectedHobby.remove(hobby);
});
print(selectedWord);
print(deSelectedWord);
}
},
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 5, vertical: 4),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 5, horizontal: 12),
decoration: BoxDecoration(
color: isSelected
? HexColor('#0000FF')
: HexColor('#D9D9D9'),
borderRadius: BorderRadius.circular(18),
border: Border.all(
color: isSelected
? HexColor('#0000FF')
: HexColor('#D9D9D9'),
width: 2)),
child: Text(
word,
style: TextStyle(
color: isSelected
? Colors.black
: Colors.black,
fontSize: 14,
fontWeight: FontWeight.w600),
),
),
),
);
},
).toList(),
),
],
),
),
],
),
),
],
))),
),
);
}
}
How get that from firestore? I hope You can understand what I ask. Thank you!
I would do the following:
Initialize your list of words to an empty list
Use the initState method of the stateful widget to make a call to firestore to fetch all the documents that have the wordName property and get the word from the result and set it to a new list
Assign the new list to the wordList property and setState to re-render.
This would be it to get the words and set the chips with fetched words.
Keep in mind that since you are making an async call to firestore you should show some form of loading to tell the user you are fetching the data otherwise you would show and empty chip list until you fetch the data.
I am trying to preselect a particular tag shown on the right of the image.
However, I am unable to figure out where to set it. The tags are coming from an API (Postgres backend).
Once it is built to a list of overlay as shown on the right again in the screenshot. I just wanted it to preselect, "Morning", "Evening" or "Daytime" based on the time of the day.
To start off with, I am not able to preselect anything in "selectedTags". This can only be done manually by the user when clicked on a tag.
The method is shared below.
showTagPicker(context, allTags) async {
await showModalBottomSheet(
isDismissible: false,
enableDrag: false,
backgroundColor: Colors.transparent,
isScrollControlled: true,
context: context,
builder: (builder) => Center(
child: Container(
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(10),
),
color: Colors.white,
),
margin: EdgeInsets.all(16),
padding: EdgeInsets.all(24),
child: ListView(
shrinkWrap: true,
children: <Widget>[
Text(
"Please pick your tags",
style: TextStyle(fontSize: 16),
),
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TagPicker(
height: MediaQuery.of(context).size.height * .6,
tags: allTags,
onTagSelected: (_selectedTags) {
selectedTags = _selectedTags;
print("----->");
print(selectedTags);
print("<-----");
},
),
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
RaisedButton(
color: PRIMARY,
textColor: WHITE,
onPressed: () {
Navigator.of(context).pop();
navigateToAnalysis(context);
},
child: Text("Save"),
),
],
)
],
),
),
),
);
}
I tried, adding "print" to debug and see what and where things are being set but I did not get any further, I have also shown the debug screen if it helps.
Any direction here to preselect one/many tags would be helpful.
Please let me know if I must provide additional details to solve this.
Also, I know there are several things in the code which might be wrong, it is inherited code and I am struggling a bit.
Edit: Including TagPicker. It is not a public library but our widget.
class TagPicker extends StatefulWidget {
const TagPicker(
{Key key, this.height, this.tags, this.onTagSelected, this.selectedTags})
: super(key: key);
#override
TagPickerState createState() => TagPickerState();
final double height;
final List tags;
final List selectedTags;
final Function onTagSelected;
}
class TagPickerState extends State<TagPicker> {
List selectedTags = [];
#override
void initState() {
super.initState();
if (widget.selectedTags != null) {
setState(() {
selectedTags = widget.selectedTags;
});
}
}
#override
Widget build(BuildContext context) {
return widget.tags != null
? Container(
constraints: widget.height != null
? BoxConstraints(maxHeight: widget.height, minHeight: 60)
: BoxConstraints(),
child: SingleChildScrollView(
child: Wrap(
spacing: 0.0,
children: List.generate(
widget.tags.length,
(index) {
return Padding(
padding: const EdgeInsets.only(right: 4.0),
child: ChoiceChip(
selectedColor: PRIMARY,
labelStyle: TextStyle(
fontSize: 12,
color: selectedTags.contains(widget.tags[index])
? WHITE
: Colors.black),
label: Text(widget.tags[index]['tag_name']),
selected: selectedTags.contains(widget.tags[index]),
onSelected: (selected) {
setState(() {
selectedTags.contains(widget.tags[index])
? selectedTags.remove(widget.tags[index])
: selectedTags.add(widget.tags[index]);
widget.onTagSelected(selectedTags);
});
},
),
);
},
),
),
),
)
: Container();
}
}
Pass selectedTags as an argument to TagPicker and modify TagPicker to render an initial set of selected tags. As before onTagSelected callback will provide an updated set.
I want to create toggle buttons and evenly space each element in the list of toggle buttons and make each selected button rounded like this,
I've tried using boxconstraints property, width property, margin property and the rest,
But this is what I'm getting, I've tried every other thing but I can't get it, this is what I'm getting
This is the code I'm using
import 'package:flutter/material.dart';
class TestingScreen extends StatefulWidget {
#override
State<TestingScreen> createState() => _TestingScreenState();
}
class _TestingScreenState extends State<TestingScreen> {
List<bool> _isSelected = [true, false, false, false];
#override
Widget build(BuildContext context) {
return Scaffold(
body: Padding(
padding: EdgeInsets.symmetric(horizontal: 24, vertical: 100),
child: Row(
children: [
Text(
'Time',
style: TextStyle(
color: Colors.black,
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
ToggleButtons(
color: Color(0xff001666),
fillColor: Color(0xff001666),
selectedColor: Colors.white,
children: [
ToggleButton(name: '1D'),
ToggleButton(name: '1W'),
ToggleButton(name: '1M'),
ToggleButton(name: '1Y'),
],
isSelected: _isSelected,
onPressed: (int newIndex) {
setState(() {
for (int i = 0; i < _isSelected.length; i++) {
if (i == newIndex) {
_isSelected[i] = true;
} else {
_isSelected[i] = false;
}
print(_isSelected);
}
});
},
)
],
),
),
);
}
}
class ToggleButton extends StatelessWidget {
final String name;
const ToggleButton({Key? key, required this.name}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(
width: MediaQuery.of(context).size.width * 0.1,
decoration: BoxDecoration(borderRadius: BorderRadius.circular(12)),
padding: EdgeInsets.symmetric(vertical: 4),
alignment: Alignment.center,
child: Text(
name,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
),
),
);
}
}
You can add renderBorder: false, property to remove the ash colored border and borderRadius: BorderRadius.circular(15), to make the round circled border in the outside and make the shape you can use constraints: const BoxConstraints.expand(height: 25,width: 34), to get the the exact size of the height and the width.enter image description here
but to get the exact result you have to use Inkwell() or ElvatedButton() or IconButton() bcz there isnt any property to use the borderRadius: BorderRadius.circular(15), for the each of the icons in the buttons as showed in the picture.
Hope it will work for you.
I am trying to make Tags which are draggable text so that I can drag them to one of 2 DragTarget I have (ideally being able to move them between 3 DragTarget). Unfortunately I can't make them interact with the DragTargets as they dont even call onWillAccept(). My draggables are DragabbleTag and extends Draggable and my dragTargets are in a Stateful Widget and should accept them.
import 'package:myApp/components/draggableTag.dart';
import 'package:flutter/material.dart';
class DraggableTagTarget extends StatefulWidget {
final String title;
final int maxTagAmount;
final Color backgroundColor;
final List<DraggableTag> tagsPool;
const DraggableTagTarget(
{Key key,
this.title,
this.backgroundColor,
this.tagsPool,
this.maxTagAmount})
: super(key: key);
#override
_DraggableTagTargetState createState() => _DraggableTagTargetState();
}
class _DraggableTagTargetState extends State<DraggableTagTarget> {
String test = "Test";
#override
Widget build(BuildContext context) {
return DragTarget<DraggableTag>(onAccept: (DraggableTag value) {
setState(() {
widget.tagsPool.add(value);
test = value.label;
});
}, onWillAccept: (DraggableTag data) {
bool result =
widget.tagsPool.length <= widget.maxTagAmount ? true : false;
debugPrint("ONWillAccept: " + data.label + " = " + result.toString());
return result;
}, builder: (context, candidateData, rejectedData) {
return Container(
decoration: new BoxDecoration(
color: widget.backgroundColor,
border: Border.all(
color: Colors.black,
),
),
child: Column(
children: <Widget>[
Text(test),
Text(widget.title),
SizedBox(
height: 60,
child: Wrap(
children: widget.tagsPool,
),
),
],
),
);
});
}
}
Custom DragTarget 'DraggableTagTarget'
import 'package:flutter/material.dart';
class DraggableTag extends Draggable<String> {
final String label;
DraggableTag({Key key, this.label})
: super(
key: key,
data: label,
child: idleTag(label),
feedback: feedbackTag(label),
childWhenDragging: ghostTag(label),
);
static Widget idleTag(String label) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8.0, vertical: 2.0),
child: Text(
label,
style: TextStyle(
fontSize: 16,
),
),
decoration: BoxDecoration(
color: Colors.blue,
border: Border.all(
color: Colors.black,
),
borderRadius: BorderRadius.all(Radius.circular(20)),
),
);
}
Custom Draggable 'DraggableTag'
I excluded feedbackTag() and ghostTag which shouldnt be relevant
At first my draggableTag was extending a widget but seeing some similar problem I made it into extending directly a Draggable but it didnt help
EDIT:
I am assigning ma values to draggable in a custom DialogWidget (stateful widget) in a list
class _RatingDialogState extends State<RatingDialog> {
List<DraggableTag> tagsPool = [
DraggableTag(label: "Acting"),
DraggableTag(label: "Scenario"),
DraggableTag(label: "Pace"),
DraggableTag(label: "Length"),
DraggableTag(label: "Message"),
];
List<DraggableTag> negativeTagsPool = [];
List<DraggableTag> positiveTagsPool = [];
#override
Widget build(BuildContext context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
elevation: 0,
backgroundColor: Colors.transparent,
child: contentBox(context),
);
}
contentBox(context) {
return Stack(
...
Wrap(
children: tagsPool,
),
SizedBox(height: 22),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Flexible(
child: FractionallySizedBox(
widthFactor: 0.85,
child: DraggableTagTarget(
title: "Negative",
backgroundColor: Colors.red,
tagsPool: negativeTagsPool,
maxTagAmount: 3),
),
),
Flexible(
child: FractionallySizedBox(
widthFactor: 0.85,
child: DraggableTagTarget(
title: "Positive",
backgroundColor: Colors.green,
tagsPool: positiveTagsPool,
maxTagAmount: 3),
),
),
]),
...
SOLUTION: as ikerfah explained, I didnt put the right type into <> because I was confused to what my DraggableTag class was. I made another class to contains the data Tag so that both my DragTarget and DraggableTag use this class
Draggable and DragTarget must have the same generic type, but you have Draggable<String> and DragTarget<DraggableTag>
I have a model class which I used to create a object from one of my main stateful class. I have a text field and button in my main class. But they both are completely different stateful class. That is, I have 3 different classes in a dart file (main page, textfield, button). I want to access and modify the object initiated in the build of main page in my textfield and button.
What I did: I have made my object on the top of all classes in my dart file assuming all classes have access to them. That was a success. All classes have access to that object, even value initiated to the object from main page are available in other classes (textfield, button).
Problem what I have now: Even though I have access to those values in the object, I can't modify it to latest value from FancyTextField class regardless StatusButton class updating.
What my project do: I will get some values from firestore database on my main page build and I will pass it to textfield and button in the other two classes., that way it resembles my values on it. And I will save the modified value from textfield and button to the object and upload it to firestore database from main page.
Complete code of the page:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/services.dart';
import 'package:om/models/machine.dart';
import 'package:om/utils/kalaicons_icons.dart';
Machine machine;
class WorkshopTool extends StatefulWidget {
final String rotary;
WorkshopTool(this.rotary);
#override
_WorkshopToolState createState() => _WorkshopToolState(rotary);
}
class _WorkshopToolState extends State<WorkshopTool> {
String rotary;
bool _showOnScreen = true;
_WorkshopToolState(this.rotary);
#override
Widget build(BuildContext context) {
var screenSize = MediaQuery.of(context).size;
return FutureBuilder(
future: Firestore.instance
.collection('productionrpt')
.document(rotary)
.get(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Container(
height: screenSize.height - 50.0,
child: Center(
child: SizedBox(
height: 80.0,
width: 80.0,
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.green[600]),
strokeWidth: 15.0,
)),
));
machine = Machine.fromMapObjext(snapshot.data);
return Container(
height: screenSize.height - 50.0,
width: screenSize.width,
child: Stack(
children: <Widget>[
SingleChildScrollView(
child: Column(
children: <Widget>[
//To make a empty column space for stack on top
Container(
height: 80.0,
padding: EdgeInsets.only(bottom: 5.0, right: 15.0),
child: Align(
alignment: Alignment.bottomRight,
child: Text(
machine.date,
style: TextStyle(
color: Colors.black,
fontSize: 17.0,
fontStyle: FontStyle.italic,
fontWeight: FontWeight.bold),
),
),
),
FancyTextField('Production'),
FancyTextField('Damages'),
FancyTextField('Plan'),
SizedBox(
height: 20.0,
),
Padding(
padding: EdgeInsets.only(
bottom:
MediaQuery.of(context).viewInsets.bottom)),
],
),
),
Container(
height: 50.0,
decoration: BoxDecoration(
color: Colors.green[400],
borderRadius:
BorderRadius.vertical(top: Radius.circular(15.0))),
),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.all(20.0),
child: MaterialButton(
color: Colors.black,
padding: EdgeInsets.only(
top: 10.0, bottom: 10.0, left: 20.0, right: 20.0),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(15.0)),
child: Text(
'UPDATE',
style: TextStyle(
fontSize: 30.0,
fontWeight: FontWeight.bold,
color: Colors.white),
),
onPressed: () {
print('Saved to cloud : ${machine.production}');
firebasePutData();
}),
),
),
Align(
alignment: Alignment.bottomLeft,
child: Padding(
padding: EdgeInsets.all(20.0),
child: StatusButton(),
),
),
Align(
alignment: Alignment.topLeft,
child: IconButton(
icon: Icon(
Icons.close,
size: 40,
color: Colors.black,
),
onPressed: () {
print('Bottomsheet closed');
}),
),
Align(
alignment: Alignment.topCenter,
child: Padding(
padding: const EdgeInsets.all(10.0),
child: Text(
machine.rotary,
style: TextStyle(
color: Colors.black,
fontSize: 20.0,
decoration: TextDecoration.underline,
fontWeight: FontWeight.bold),
),
),
),
Padding(
padding: const EdgeInsets.all(4.0),
child: Align(
alignment: Alignment.topRight,
child: Switch(
value: _showOnScreen,
activeTrackColor: Colors.black54,
activeColor: Colors.black,
inactiveThumbColor: Colors.grey[600],
inactiveTrackColor: Colors.grey[500],
onChanged: (v) {
_showOnScreen = !_showOnScreen;
print('Switch tapped');
}),
),
),
],
),
);
});
}
void firebasePutData() {
Firestore.instance
.collection("productionrpt")
.document(rotary)
.updateData(machine.toMap());
print('Data updated');
}
} //End of main page STATE (a bottom sheet)
//#######################################################################################################
//############ FANCY TEXT FIELD FOR ENTERING MACHINE DATA #################
class FancyTextField extends StatefulWidget {
final String _title;
FancyTextField(
this._title,
);
#override
_FancyTextFieldState createState() => _FancyTextFieldState(this._title);
}
class _FancyTextFieldState extends State<FancyTextField> {
final String _title;
final TextEditingController _ctrl = TextEditingController();
_FancyTextFieldState(this._title);
#override
void initState() {
switch (_title) {
case 'Production':
_ctrl.text = machine.production.toString();
break;
case 'Plan':
_ctrl.text = machine.plan.toString();
break;
case 'Damages':
_ctrl.text = machine.damage.toString();
break;
}
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
height: 125.0,
margin: EdgeInsets.all(8.0),
decoration: BoxDecoration(
color: Colors.green[400],
borderRadius: BorderRadius.circular(15.0),
// boxShadow: [
// BoxShadow(
// blurRadius: 5, color: Colors.green[300], spreadRadius: 5)
// ]
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
_title,
style: TextStyle(
color: Colors.black,
fontSize: 23.0,
fontWeight: FontWeight.bold),
),
Container(
height: 50,
width: 300,
alignment: Alignment.center,
padding: const EdgeInsets.all(5.0),
margin: const EdgeInsets.only(
top: 10.0, bottom: 10, left: 30.0, right: 30.0),
decoration: BoxDecoration(
color: Colors.white70,
borderRadius: BorderRadius.circular(10),
),
child: TextField(
//maxLength: 5,
controller: _ctrl,
textAlign: TextAlign.center,
keyboardType: TextInputType.number,
style: TextStyle(
color: Colors.black,
fontSize: 30.0,
),
decoration: InputDecoration(
border: InputBorder.none,
),
onChanged: (v) {
switch (_title) {
case 'Production':
machine.production = int.parse(_ctrl.text);
break;
case 'Plan':
machine.plan = int.parse(_ctrl.text);
break;
case 'Damages':
machine.damage = int.parse(_ctrl.text);
break;
}
print('Prod: ${machine.production}');
},
),
),
],
),
);
}
} //END OF CLASS FANCY TEXT FIELD
//######################################################################################################
//####### A STATEFUL WIDGET FOR MACHINE STATUS BUTTON : running, off, breakdown ##########
class StatusButton extends StatefulWidget {
#override
_StatusButtonState createState() => _StatusButtonState();
}
class _StatusButtonState extends State<StatusButton> {
Color color;
IconData icon;
#override
Widget build(BuildContext context) {
switch (machine.stats) {
case 0:
color = Colors.grey[600];
icon = Icons.power_settings_new;
break;
case 1:
color = Colors.blue;
icon = Icons.power_settings_new;
break;
default:
color = Colors.red;
icon = Kalaicons.breakdown;
break;
}
return Container(
height: 70.0,
width: 70.0,
decoration: BoxDecoration(
color: color,
shape: BoxShape.circle,
),
child: IconButton(
icon: Icon(
icon,
color: Colors.white,
size: 50.0,
),
onPressed: () {
setState(() {
machine.stats > 1 ? machine.stats = 0 : machine.stats++;
print('Status button pressed: ${machine.stats}');
});
},
));
}
} //END OF CLASS STATUS BUTTON
My model:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:intl/intl.dart';
class Machine {
int _production;
int _plan;
int _damage;
int _stats = 0;
String _date = '~Not available';
String _rotary;
// Machine(this._production, this._damage, this._date,
// [this._stats, this._plan]);
int get production => this._production;
int get plan => this._plan;
int get damage => this._damage;
int get stats => this._stats;
String get date => this._date;
String get rotary => this._rotary;
set production(int updatedValue) {
if (updatedValue != null) {
this._production = updatedValue;
}
}
set plan(int updatedValue) {
if (updatedValue != null) {
this._plan = updatedValue;
}
}
set damage(int updatedValue) {
if (updatedValue != null) {
this._damage = updatedValue;
}
}
set stats(int updatedValue) {
this._stats = updatedValue;
}
// set date(String updatedValue) {
// this._date = DateFormat.jm().format(DateTime.now());
//
// }
//Function to set a map list of new data for firebase
Map<String, dynamic> toMap() {
var map = Map<String, dynamic>();
map['production'] = this._production;
map['plan'] = this._plan;
map['damages'] = this._damage;
map['stats'] = this._stats;
map['date'] = DateFormat("MMMM dd, hh:mm a").format(DateTime.now());
return map;
}
//Constructor to extract firebase collections
Machine.fromMapObjext(DocumentSnapshot map) {
this._production = map['production'];
this._plan = map['plan'];
this._damage = map['damages'];
this._stats = map['stats'];
this._date = map['date'];
this._rotary = map['machine'];
}
}
UPDATE I RECENTLY NOTED: The value of StatusButton is gettiong updated to the object and to firestore. however value of updated FancyTextField only reflects inside that class itself. Not updating globally.
Lastly I found the issue myself rolling 3 days with it. Issue was happened because when keyboard pops, the widget also gets it state rebuild. Since I had my firebase data on FutureBuilder in the build, my old data was recalled from the firebase again and save it on top of newly edited data.
Why initial text I put on the TextField didn't changed ?
Because I had it set it on my initState of FancyTextField class, so when rebuilding widget I won't execute and my edited value stays as before.
Why StatusButton value updated without failing ?
Because when I tap on the button keyboard doesn't pops up and build wasn't rebuild again. But later I noticed, after changing my StatusButton status to someother value and tapping on TextField makes it to OLD value (that is the value that is in firebase currently). Since build is recreated. That's how I figured it out.
What I did to overcome this:
I simply removed my FutureBuilder which gets data from firebase, and created a Future for the same and initiated in the initState.
If anyone want, I can show the updated code here