I have a loop that prints "hello world" 100 times. so what i want is to show progress Indicator which shows the loop progress..
for example: if the loop printed 50 time from total 100 time the progress Indicator should be at 50 percent..
Like #pskink mentioned in the comments, a LinearProgressIndicator or CircularProgressIndicator should do the trick. Do go into a bit more detail, you can store the progress after each iteration (or every so many iterations depending on your needs), and if you use your widget's state for it, a rebuild should automatically trigger and rebuild the progress indicator with the new value each time. That could look a bit like:
// inside the State
double progress = 0.0;
doTheLoop() {
for (int i = 0; i < 100; i++) {
print('Hello world');
setState(() => progress = i/100);
}
}
build(BuildContext context) {
return Column(children: [
Container(
// the progress indicator updates when the progress variable in the state updates, since a rebuild is triggered
child: LinearProgressIndicator(
progress: progress,
);
),
// press the button to start the loop
ElevatedButton(
child: Text('Start loop'),
onPressed: doTheLoop,
),
],),
}
Here Direct Setstate not work because for loop execute as soon as possible.So we add 100 millisecond time delay for visual progress
await Future.delayed(Duration(milliseconds: 100));
Linewar Widget
LinearProgressIndicator(
minHeight: 25,
value: _value,
color: _color,
semanticsValue: (_value * 100).toString(),
semanticsLabel: (_value * 100).toString(),
)
while Press loopbutton
Future<void> loop() async {
for (int i = 0; i <= 100; i++) {
await Future.delayed(Duration(milliseconds: 100));
var element = i;
print(element);
setState(() {
_value = element / 100;
print(_value);
});
if (element < 5 && element > 0)
_color = Colors.red;
else if (element < 25 && element > 5)
_color = Colors.cyan;
else if (element < 50 && element > 25)
_color = Colors.lightGreenAccent;
else if (element < 75 && element > 50)
_color = Colors.lightGreen;
else if (element < 100 && element > 75) _color = Colors.green;
}
}
Without Streamcontroller dartpad
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => Home(),
},
title: _title,
// home: ,
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("title")),
body: const Center(
child: MyStatelessWidget(),
),
);
}
}
var _color = Colors.black;
var _value = 0.0;
class MyStatelessWidget extends StatefulWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
State<MyStatelessWidget> createState() => _MyStatelessWidgetState();
}
class _MyStatelessWidgetState extends State<MyStatelessWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(8),
child: Stack(
children: [
Positioned(
child: Container(
height: 100,
width: 100,
child: CircularProgressIndicator(
strokeWidth: 4,
value: _value,
color: _color,
),
),
top: 0,
left: 0,
right: 0,
bottom: 0,
),
Positioned(
child: Text(
(_value * 100).toStringAsFixed(1),
style: TextStyle(fontSize: 15),
),
top: 25,
left: 10,
),
],
),
height: 75,
width: 75,
),
Container(
padding: EdgeInsets.all(8),
child: LinearProgressIndicator(
minHeight: 25,
value: _value,
color: _color,
semanticsValue: (_value * 100).toString(),
semanticsLabel: (_value * 100).toString(),
)),
Text(
(_value * 100).toStringAsFixed(1),
style: TextStyle(fontSize: 25),
),
Row(
children: [
Expanded(
child: IconButton(
onPressed: () {
loop();
},
icon: Icon(
Icons.not_started_outlined,
size: 45,
),
),
),
Expanded(
child: IconButton(
onPressed: () {},
icon: Icon(Icons.stop, size: 45),
),
),
],
),
],
),
);
}
Future<void> loop() async {
for (int i = 0; i <= 100; i++) {
// if (!stream.isClosed) stream.sink.addStream(Stream.value(i));
await Future.delayed(Duration(milliseconds: 100));
var element = i;
print(element);
setState(() {
_value = element / 100;
print(_value);
});
if (element < 5 && element > 0)
_color = Colors.red;
else if (element < 25 && element > 5)
_color = Colors.cyan;
else if (element < 50 && element > 25)
_color = Colors.lightGreenAccent;
else if (element < 75 && element > 50)
_color = Colors.lightGreen;
else if (element < 100 && element > 75) _color = Colors.green;
}
}
#override
void initState() {}
}
This Sample .Here use streamcontroller .so using stream controller mange the progress like pause or stop the progress.
SampleCode Dart pad Live code
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: {
"/": (context) => Home(),
},
title: _title,
// home: ,
);
}
}
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("title")),
body: const Center(
child: MyStatelessWidget(),
),
);
}
}
var _color = Colors.black;
var _value = 0.0;
class MyStatelessWidget extends StatefulWidget {
const MyStatelessWidget({Key? key}) : super(key: key);
#override
State<MyStatelessWidget> createState() => _MyStatelessWidgetState();
}
class _MyStatelessWidgetState extends State<MyStatelessWidget> {
#override
Widget build(BuildContext context) {
return Container(
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.all(8),
child: Stack(
children: [
Positioned(
child: Container(
height: 100,
width: 100,
child: CircularProgressIndicator(
strokeWidth: 4,
value: _value,
color: _color,
),
),
top: 0,
left: 0,
right: 0,
bottom: 0,
),
Positioned(
child: Text(
(_value * 100).toStringAsFixed(1),
style: TextStyle(fontSize: 15),
),
top: 25,
left: 10,
),
],
),
height: 75,
width: 75,
),
Container(
padding: EdgeInsets.all(8),
child: LinearProgressIndicator(
minHeight: 25,
value: _value,
color: _color,
semanticsValue: (_value * 100).toString(),
semanticsLabel: (_value * 100).toString(),
)),
Text(
(_value * 100).toStringAsFixed(1),
style: TextStyle(fontSize: 25),
),
Row(
children: [
Expanded(
child: IconButton(
onPressed: () {
loop();
},
icon: Icon(
Icons.not_started_outlined,
size: 45,
),
),
),
Expanded(
child: IconButton(
onPressed: () {
stream.close();
},
icon: Icon(Icons.stop, size: 45),
),
),
],
),
],
),
);
}
Future<void> loop() async {
for (int i = 0; i <= 100; i++) {
if (!stream.isClosed) stream.sink.addStream(Stream.value(i));
await Future.delayed(Duration(milliseconds: 100));
}
// List.generate(100, (index) => index + 1).forEach((element) async {
// if (!stream.isClosed) stream.sink.addStream(Stream.value(element));
// await Future.delayed(Duration(seconds: 1));
// });
}
// late StreamController<int> stream;
StreamController<int> stream = StreamController();
#override
void initState() {
stream.stream.listen((element) {
print(element);
setState(() {
_value = element / 100;
print(_value);
});
if (element < 5 && element > 0)
_color = Colors.red;
else if (element < 25 && element > 5)
_color = Colors.cyan;
else if (element < 50 && element > 25)
_color = Colors.lightGreenAccent;
else if (element < 75 && element > 50)
_color = Colors.lightGreen;
else if (element < 100 && element > 75) _color = Colors.green;
});
}
}
please try this
class testwidget extends StatefulWidget {
const testwidget({Key? key}) : super(key: key);
#override
_test createState() => _test();
}
class _test extends State<testwidget> {
StreamController loopValueStram=new StreamController();
var loopProgress=0.0;
static const max=100;
#override
void initState() {
// TODO: implement initState
super.initState();
loopValueStram.stream.listen((event) {
setState(() {
loopProgress=event;
});
});
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('test'),
),
body: Center(
child:Column(
children: [
RaisedButton(
onPressed: (){
loop();
},
child: Text("Start loop ${(loopProgress*100).toInt()}%"),
),
SizedBox(height: 10,),
Padding(padding: EdgeInsets.all(20),
child: Visibility(
visible:loopProgress>0,
child: LinearProgressIndicator(
value: loopProgress.toDouble(),
semanticsLabel: "Progress",
minHeight: 40,
)),
)
],
)
)
);
}
Future<void> loop() async {
for(int i=0;i<=100;i++){
loopValueStram.sink.add(i/100);
await Future.delayed(Duration(seconds: 1));// this line is to slowdown itération so we can see linear pregression well
}
}
}
Related
I want to drag and drop my custom widget with gesture detector. It is showing x- direction and y- direction values but not dragging to anywhere on screen.
Here is my code:
layoutpage:
SizedBox(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Stack(
children: _layoutProvider.tables
.map(
(SectionTable sTable) => Positioned(
top: sTable.top,
left: sTable.left,
child: LayoutWidget(
width: sTable.width,
height: sTable.height,
type: sTable.type.index,
name: sTable.name,
left: sTable.left,
top: sTable.top,
rotate: sTable.rotate,
color: sTable.order != null
? Colors.green
: Colors.grey,
seats: sTable.seats,
),
),
)
.toList()),
),
LayoutWidget:
class LayoutWidget extends StatefulWidget {
late double width;
late double height;
late double left;
late double top;
LayoutWidget({
Key? key,
required this.width,
required this.height,
required this.left,
required this.top,
}) : super(key: key);
#override
State<StatefulWidget> createState() => _LayoutWidgetState();
}
class _LayoutWidgetState extends State<LayoutWidget> {
#override
Widget build(BuildContext context) {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
RotationTransition(
turns: widget.type == 0
? const AlwaysStoppedAnimation(0)
: AlwaysStoppedAnimation(rotationValue / 360),
child: GestureDetector(
onPanUpdate: (details) {
widget.top = widget.top+ details.delta.dy;
widget.left = widget.left+ details.delta.dx;
setState(() {
});
},
onTap: () {
setState(() {
showMenu = !showMenu;
});
},
child: myWidget()
}
Can anyone help why i am unable to drag on screen. Thanks.
I hope you you can get Idea from this code. In this code you can drag Container anywhere in the Screen and set it. And also check this Gesture Detector Overview for Gesture Detector detail.
import 'package:flutter/cupertino.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class GestureDetectorPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: Colors.grey[100],
width: double.infinity,
height: double.infinity,
child: MainContent(),
),
);
}
}
class MainContent extends StatefulWidget {
#override
_MainContentState createState() => _MainContentState();
}
class _MainContentState extends State<MainContent> {
GlobalKey key = GlobalKey();
String dragDirection = '';
String startDXPoint = '50';
String startDYPoint = '50';
String dXPoint;
String dYPoint;
String velocity;
#override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragStart: _onHorizontalDragStartHandler,
onVerticalDragStart: _onVerticalDragStartHandler,
onHorizontalDragUpdate: _onDragUpdateHandler,
onVerticalDragUpdate: _onDragUpdateHandler,
onHorizontalDragEnd: _onDragEnd,
onVerticalDragEnd: _onDragEnd,
dragStartBehavior: DragStartBehavior.start, // default
behavior: HitTestBehavior.translucent,
child: Stack(
children: [
Positioned(
left: double.parse(this.startDXPoint),
top: double.parse(this.startDYPoint),
child: Container(
decoration: BoxDecoration(
color: Colors.green,
borderRadius: BorderRadius.circular(100)
),
child: Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Text('Draggable', style: TextStyle(fontSize: 14, color: Colors.white),),
),
),
)
),
],
),
);
}
void _onHorizontalDragStartHandler(DragStartDetails details) {
setState(() {
this.dragDirection = "HORIZONTAL";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
/// Track starting point of a vertical gesture
void _onVerticalDragStartHandler(DragStartDetails details) {
setState(() {
this.dragDirection = "VERTICAL";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
void _onDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "UPDATING";
this.startDXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.startDYPoint = '${details.globalPosition.dy.floorToDouble()}';
});
}
/// Track current point of a gesture
void _onHorizontalDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "HORIZONTAL UPDATING";
this.dXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.dYPoint = '${details.globalPosition.dy.floorToDouble()}';
this.velocity = '';
});
}
/// Track current point of a gesture
void _onVerticalDragUpdateHandler(DragUpdateDetails details) {
setState(() {
this.dragDirection = "VERTICAL UPDATING";
this.dXPoint = '${details.globalPosition.dx.floorToDouble()}';
this.dYPoint = '${details.globalPosition.dy.floorToDouble()}';
this.velocity = '';
});
}
/// What should be done at the end of the gesture ?
void _onDragEnd(DragEndDetails details) {
double result = details.velocity.pixelsPerSecond.dx.abs().floorToDouble();
setState(() {
this.velocity = '$result';
});
}
}
You can use the Draggable widget instead. Please try this
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
static const String _title = 'Flutter Code Sample';
#override
Widget build(BuildContext context) {
return MaterialApp(
title: _title,
home: Scaffold(
appBar: AppBar(title: const Text(_title)),
body: const MyStatefulWidget(),
),
);
}
}
class MyStatefulWidget extends StatefulWidget {
const MyStatefulWidget({Key? key}) : super(key: key);
#override
State<MyStatefulWidget> createState() => _MyStatefulWidgetState();
}
class _MyStatefulWidgetState extends State<MyStatefulWidget> {
int acceptedData = 0;
#override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
Draggable<int>(
// Data is the value this Draggable stores.
data: 10,
feedback: Container(
color: Colors.deepOrange,
height: 100,
width: 100,
child: const Icon(Icons.directions_run),
),
childWhenDragging: Container(
height: 100.0,
width: 100.0,
color: Colors.pinkAccent,
child: const Center(
child: Text('Child When Dragging'),
),
),
child: Container(
height: 100.0,
width: 100.0,
color: Colors.lightGreenAccent,
child: const Center(
child: Text('Draggable'),
),
),
),
DragTarget<int>(
builder: (
BuildContext context,
List<dynamic> accepted,
List<dynamic> rejected,
) {
return Container(
height: 100.0,
width: 100.0,
color: Colors.cyan,
child: Center(
child: Text('Value is updated to: $acceptedData'),
),
);
},
onAccept: (int data) {
setState(() {
acceptedData += data;
});
},
),
],
);
}
}
I have checked mukltiple posts on this matter, but none has helped, while some stopped the error (e.g. adding a variable that stops the function to notifylistener after its disposed), it certainly didnt fix it.
My Goal is to make a shopping list that just records the state of each checkbox in a tab and displays it using navigator.push and back using pop
Source Code:
home.dart
// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';
import 'package:loginpage/main.dart';
import 'package:badges/badges.dart';
import 'package:provider/provider.dart';
import 'tabPage.dart';
import 'mobileList.dart';
import 'itemList.dart';
class HomePage extends StatefulWidget {
HomePage({Key? key}) : super(key: key);
#override
State<HomePage> createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
LoginPage loginPage = LoginPage();
TabPage? tabPage;
#override
void initState() {
super.initState();
tabPage = TabPage(
key: GlobalKey<TabPageState>(),
tabNum: 10,
controlNum: 3,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Home Page"),
leading: IconButton(
onPressed: () {
//Back to Login Page
Navigator.push(
context, MaterialPageRoute(builder: (context) => LoginPage()));
},
icon: Image.asset('assets/images/backbtn.png'),
),
actions: [
ChangeNotifierProvider(
create: (context) => tabPage!.checkBoxInfo,
child: Consumer<CheckBoxInfo>(
builder: (context, checkBoxInfo, child) {
return Badge(
position: BadgePosition.topEnd(top: 0, end: 0),
shape: BadgeShape.square,
borderRadius: BorderRadius.circular(20),
badgeContent: Text(checkBoxInfo.checked.toString()),
animationType: BadgeAnimationType.scale,
animationDuration: Duration(milliseconds: 50),
child: IconButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ItemList(
checkBoxInfo: tabPage!.checkBoxInfo)));
},
icon: Icon(Icons.shopping_cart)),
);
},
),
),
],
),
body: SafeArea(
child: Scaffold(
body: Center(
child: Column(
children: <Widget>[
SizedBox(height: 30),
Text('This is the Home Page'),
SizedBox(height: 20),
Text(loginPage.username),
//Tab Page
Container(
child: tabPage,
),
TextButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MobileList(data: data)));
},
child: Text("Mobile List")),
],
),
),
),
),
floatingActionButton: Container(
decoration:
BoxDecoration(color: Colors.grey[800], shape: BoxShape.circle),
child: IconButton(
color: Colors.lightBlue,
onPressed: () {
// tabPage!.tabindex == tabPage!.tabNum - 1
// ? tabPage!.tabindex = 0
// : tabPage!.tabindex += 1;
// //callback method
// //GlobalKey method
// //Set Global Key
// (tabPage!.key as GlobalKey<TabPageState>) //Casting
// .currentState
// ?.tabSetState();
// // tabPageKey.currentState?.tabSetState();
(tabPage!.key as GlobalKey<TabPageState>)
.currentState!
.scrollToOffset(100, Duration(milliseconds: 200));
},
icon: const Icon(Icons.ac_unit_outlined)),
),
);
}
}
tabPage.dart
import 'package:flutter/material.dart';
import 'dart:developer';
import 'package:loginpage/pages/home.dart';
class TabPage extends StatefulWidget {
TabPage({Key? key, required this.tabNum, required this.controlNum})
: super(key: key);
final int controlNum;
final int tabNum;
final String _example = "";
CheckBoxInfo checkBoxInfo = CheckBoxInfo();
int tabindex = 0;
#override
State<StatefulWidget> createState() => TabPageState();
}
class TabPageState extends State<TabPage> {
List<String> tabTitles = [];
List<List<Widget>> tabs = [];
ScrollController? _scrollController;
void scrollToOffset(increment, duration) {
_scrollController!.animateTo(_scrollController!.offset + increment,
duration: duration, curve: Curves.ease);
}
void tabSetState() {
setState(() {});
}
#override
void initState() {
super.initState();
tabTitles = createTabTitles(widget.tabNum);
_scrollController = ScrollController(initialScrollOffset: 20);
_scrollController!.addListener(() {
print(_scrollController!.offset);
});
widget.checkBoxInfo
.initCheckBox(widget.controlNum, widget.tabNum, tabTitles);
}
#override
Widget build(BuildContext context) {
tabs = createTabs(widget.tabNum);
return SafeArea(
child: Column(
children: [
SizedBox(
height: 10,
),
Container(
margin: EdgeInsets.only(left: 20.0, right: 20.0),
decoration: BoxDecoration(
border: Border.all(width: 3, color: Colors.black)),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _scrollController,
child: Row(
// children: tabTitles
// .asMap()
// .map((i, title) => MapEntry(
// i,
// TextButton(
// onPressed: () {
// setState(() {
// widget.tabindex = i;
// });
// },
// child: Text(tabTitles[i]),
// )))
// .values
// .toList()
// children: tabTitles.map((title) => TextButton(
// onPressed: () {
// setState(() {
// widget.tabindex = i;
// });
// },
// child: Text(tabTitles[i]),
// )).toList(),
children: createTabBtns(),
),
),
Padding(
padding: EdgeInsets.all(5),
child: Column(
children: tabs[widget.tabindex],
),
)
]),
),
],
),
);
}
List<Widget> createTabBtns() {
List<Widget> btnList = [];
for (int i = 0; i < tabTitles.length; i++) {
btnList.add(Container(
decoration: BoxDecoration(
border: Border(
right: BorderSide(width: 1, color: Colors.grey),
bottom: widget.tabindex == i
? BorderSide(width: 4, color: Colors.blue[400]!)
: BorderSide(width: 4, color: Colors.grey[400]!))),
child: TextButton(
onPressed: () {
setState(() {
widget.tabindex = i;
});
},
child: Text(tabTitles[i]),
),
));
}
return btnList;
}
List<List<Widget>> createTabs(tabNum) {
List<List<Widget>> tabList = [];
for (int i = 0; i < tabNum; i++) {
List<Widget> tabContent = [
Align(
alignment: Alignment.centerLeft,
child: Text(
tabTitles[i] + " Items",
),
),
...createCheckBoxList(widget.controlNum, tabTitles[i])
];
tabList.add(tabContent);
}
return tabList;
}
List<Widget> createCheckBoxList(controlNum, tabTitle) {
List<Widget> CBList = [];
for (int i = 0; i < controlNum; i++) {
String checkBoxName = '${tabTitle} Item $i';
CBList.add(CheckboxListTile(
title: Text('${tabTitle} Item $i'),
value: widget.checkBoxInfo.isChecked(checkBoxName),
onChanged: (newValue) {
setState(() {
widget.checkBoxInfo.setCheckBox(checkBoxName, newValue!);
});
}));
}
return CBList;
}
List<String> createTabTitles(tabNum) {
List<String> tabTitles = [];
for (int i = 0; i < tabNum; i++) {
tabTitles.add("A" + i.toString());
}
return tabTitles;
}
}
class CheckBoxInfo extends ChangeNotifier {
Map<String, bool> checkBoxState = {};
int checked = 0;
bool disposed = false;
void setCheckBox(String name, bool state) {
checkBoxState[name] = state;
checkChecked();
notifyListeners();
}
bool isChecked(String name) {
return checkBoxState[name]!;
}
void initCheckBox(int controlNum, int tabNum, List<String> tabTitles) {
for (int i = 0; i < tabNum; i++) {
String checkBoxName = '${tabTitles[i]} ';
for (int i = 0; i < controlNum; i++) {
setCheckBox(checkBoxName + 'Item $i', false);
}
}
}
void checkChecked() {
int _checked = 0;
checkBoxState.forEach((key, value) {
_checked += value == true ? 1 : 0;
});
checked = _checked;
}
}
itemList.dart
import 'dart:collection';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:loginpage/pages/tabPage.dart';
import 'dart:developer';
import 'package:provider/provider.dart';
import 'home.dart';
class ItemList extends StatefulWidget {
ItemList({Key? key, required this.checkBoxInfo}) : super(key: key);
final CheckBoxInfo checkBoxInfo;
#override
State<StatefulWidget> createState() => ItemListState();
}
class ItemListState extends State<ItemList> {
List<Widget> itemList = [];
#override
void initState() {
super.initState();
itemList = setItemList(widget.checkBoxInfo);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Item List"),
leading: IconButton(
onPressed: () {
//Back to Home Page
Navigator.pop(context);
},
icon: Image.asset('assets/images/backbtn.png'),
),
),
body: SafeArea(
child: Container(
child: ChangeNotifierProvider(
create: (context) => widget.checkBoxInfo,
child: Consumer<CheckBoxInfo>(
builder: (context, checkBoxInfo, child) {
return Column(
children: itemList,
);
},
),
))),
);
}
List<Widget> setItemList(CheckBoxInfo checkBoxInfo) {
List<Widget> tempItemList = [];
checkBoxInfo.checkBoxState.forEach((key, value) {
if (checkBoxInfo.checkBoxState[key]!) {
tempItemList.add(Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 2),
),
child: Padding(
padding: EdgeInsets.all(8),
child: Row(
children: [
Text(
key,
overflow: TextOverflow.ellipsis,
),
SizedBox(
width: 20,
),
Icon(value ? Icons.check : Icons.cancel_outlined)
],
),
),
));
}
});
return tempItemList;
}
}
THe error is the following: A CheckBoxInfo was used after being disposed.
I dont know why it doesnt work, and according to my senior, the provider should work sort of like the local storage in web.
I think there is all sorts of things going wrong here.
You create your TabPage widget on the fly, and you put your state - checkBoxInfo - in it. And then you expose it through ChangeNotifierProvider. After that you add the TabPage to the Widget tree - which will eventually create it's state object...
As soon as you navigate from the page that shows your TabPage, it's state object and the widget itself will be disposed. And your ChangeNotifierProvider is gone, too.
The thing is: it's not your ChangeNotifierProvider that is holding your state, it is the widget underneath it.
Edit: here's the code that should work. I commented out some navigation (Login Page etc.) to make it work.
In the code, ChangeNotifierProvider is above the MaterialApp - making sure it is visible in all pages you try to navigate to.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
const Color darkBlue = Color.fromARGB(255, 18, 32, 47);
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<CheckBoxInfo>(
create: (context) => CheckBoxInfo(3, 10), //tabPage!.checkBoxInfo,
child: MaterialApp(
theme: ThemeData.dark().copyWith(
scaffoldBackgroundColor: darkBlue,
),
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: HomePage(),
),
),
));
}
}
class HomePage extends StatelessWidget {
HomePage({Key? key}) : super(key: key);
final _tabKey = GlobalKey<TabPageState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Home Page"),
leading: IconButton(
onPressed: () {
//Back to Login Page
// Navigator.push(
// context, MaterialPageRoute(builder: (context) => LoginPage()));
},
icon: const Icon(Icons.arrow_back), //.asset('assets/images/backbtn.png'),
),
actions: [
Consumer<CheckBoxInfo>(
builder: (context, checkBoxInfo, child) {
return IconButton(
onPressed: () {
Navigator.push(context, MaterialPageRoute(builder: (context) => const ItemList()));
},
icon: const Icon(Icons.shopping_cart),
);
},
),
]),
body: SafeArea(
child: Scaffold(
body: Center(
child: Column(
children: <Widget>[
const SizedBox(height: 30),
const Text('This is the Home Page'),
const SizedBox(height: 20),
const Text("Test"),
//Tab Page
Consumer<CheckBoxInfo>(builder: (context, checkBoxInfo, child) {
return TabPage(key: _tabKey, tabNum: checkBoxInfo.tabNum, controlNum: checkBoxInfo.controlNum);
}),
TextButton(
onPressed: () {
// Navigator.push(
// context,
// MaterialPageRoute(
// builder: (context) => MobileList(data: data)));
},
child: const Text("Mobile List")),
],
),
),
),
),
floatingActionButton: Container(
decoration: BoxDecoration(color: Colors.grey[800], shape: BoxShape.circle),
child: IconButton(
color: Colors.lightBlue,
onPressed: () {
_tabKey.currentState!.scrollToOffset(100, const Duration(milliseconds: 200));
},
icon: const Icon(Icons.ac_unit_outlined)),
),
);
}
}
class TabPage extends StatefulWidget {
const TabPage({Key? key, required this.tabNum, required this.controlNum}) : super(key: key);
final int controlNum;
final int tabNum;
#override
State<StatefulWidget> createState() => TabPageState();
}
class TabPageState extends State<TabPage> {
List<List<Widget>> tabs = [];
ScrollController? _scrollController;
int tabindex = 0;
void scrollToOffset(increment, duration) {
_scrollController!.animateTo(_scrollController!.offset + increment, duration: duration, curve: Curves.ease);
}
void tabSetState() {
setState(() {});
}
#override
void initState() {
super.initState();
_scrollController = ScrollController(initialScrollOffset: 20);
_scrollController!.addListener(() {
print(_scrollController!.offset);
});
}
#override
Widget build(BuildContext context) {
var checkBoxInfo = Provider.of<CheckBoxInfo>(context, listen: false);
tabs = createTabs(checkBoxInfo);
return SafeArea(
child: Column(
children: [
const SizedBox(
height: 10,
),
Container(
margin: const EdgeInsets.only(left: 20.0, right: 20.0),
decoration: BoxDecoration(border: Border.all(width: 3, color: Colors.black)),
child: Column(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _scrollController,
child: Row(
children: createTabBtns(checkBoxInfo.tabTitles),
),
),
Padding(
padding: const EdgeInsets.all(5),
child: Column(
children: tabs[tabindex],
),
)
]),
),
],
),
);
}
List<Widget> createTabBtns(List<String> tabTitles) {
List<Widget> btnList = [];
for (int i = 0; i < widget.tabNum; i++) {
btnList.add(Container(
decoration: BoxDecoration(
border: Border(
right: const BorderSide(width: 1, color: Colors.grey),
bottom: tabindex == i
? BorderSide(width: 4, color: Colors.blue[400]!)
: BorderSide(width: 4, color: Colors.grey[400]!))),
child: TextButton(
onPressed: () {
setState(() {
tabindex = i;
});
},
child: Text(tabTitles[i]),
),
));
}
return btnList;
}
List<List<Widget>> createTabs(CheckBoxInfo checkBoxInfo) {
List<List<Widget>> tabList = [];
for (int i = 0; i < widget.tabNum; i++) {
List<Widget> tabContent = [
Align(
alignment: Alignment.centerLeft,
child: Text(
checkBoxInfo.tabTitles[i] + " Items",
),
),
...createCheckBoxList(checkBoxInfo, checkBoxInfo.tabTitles[i])
];
tabList.add(tabContent);
}
return tabList;
}
List<Widget> createCheckBoxList(checkBoxInfo, tabTitle) {
List<Widget> cBList = [];
for (int i = 0; i < widget.controlNum; i++) {
String checkBoxName = '$tabTitle Item $i';
cBList.add(CheckboxListTile(
title: Text('$tabTitle Item $i'),
value: checkBoxInfo.isChecked(checkBoxName),
onChanged: (newValue) {
setState(() {
checkBoxInfo.setCheckBox(checkBoxName, newValue!);
});
}));
}
return cBList;
}
}
class CheckBoxInfo extends ChangeNotifier {
Map<String, bool> checkBoxState = {};
int checked = 0;
// bool disposed = false;
List<String> tabTitles = [];
int controlNum;
int tabNum;
CheckBoxInfo(this.controlNum, this.tabNum) {
for (int i = 0; i < tabNum; i++) {
String tabTitle = "A" + i.toString();
tabTitles.add(tabTitle);
for (int i = 0; i < controlNum; i++) {
setCheckBox(tabTitle + ' Item $i', false);
}
}
}
void setCheckBox(String name, bool state) {
checkBoxState[name] = state;
checkChecked();
notifyListeners();
}
bool isChecked(String name) {
return checkBoxState[name]!;
}
void checkChecked() {
int _checked = 0;
checkBoxState.forEach((key, value) {
_checked += value == true ? 1 : 0;
});
checked = _checked;
}
}
class ItemList extends StatelessWidget {
const ItemList({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
var checkBoxInfo = Provider.of<CheckBoxInfo>(context, listen: false);
var itemList = setItemList(checkBoxInfo);
return Scaffold(
appBar: AppBar(
title: const Text("Item List"),
leading: IconButton(
onPressed: () {
//Back to Home Page
Navigator.pop(context);
},
icon: const Icon(Icons.arrow_back),
),
),
body: SafeArea(
child: Consumer<CheckBoxInfo>(
builder: (context, checkBoxInfo, child) {
return Column(
children: itemList,
);
},
),
));
}
List<Widget> setItemList(CheckBoxInfo checkBoxInfo) {
List<Widget> tempItemList = [];
checkBoxInfo.checkBoxState.forEach((key, value) {
if (value) {
tempItemList.add(Container(
width: double.infinity,
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 2),
),
child: Padding(
padding: const EdgeInsets.all(8),
child: Row(
children: [
Text(
key,
overflow: TextOverflow.ellipsis,
),
const SizedBox(
width: 20,
),
Icon(value ? Icons.check : Icons.cancel_outlined)
],
),
),
));
}
});
return tempItemList;
}
}
#override
void dispose() {
_disposed = true;
super.dispose();
}
#override
void notifyListeners() {
if (!_disposed) {
super.notifyListeners();
}
}
void _f1() {
setState(() {
//clear output
_str = '';
});
for(int i=1; i<=5; i++) {
Future.delayed(Duration(seconds: 1), () {
setState(() {
_str += '$i ';
});
});
}
}
Now 1 2 3 4 5 output all at once.
How output values each 1 second? For example: 1 (passed 1 second), 2 (passed 1 second), 3 (passed 1 second), 4 (passed 1 second), 5 (passed 1 second) ? Thank you. Full code.
main.dart
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Name App',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Name Page'),
),
body: MyHomePage(),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _str;
#override
void initState() {
super.initState();
_str = '';
}
void _f1() {
setState(() {
//clear output
_str = '';
});
for(int i=1; i<=5; i++) {
Future.delayed(Duration(seconds: 1), () {
setState(() {
_str += '$i ';
});
});
}
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
Align(
alignment: Alignment.topLeft,
child: Container(
width: double.infinity,
height: 45.0,
//color: Colors.pink,
margin: const EdgeInsets.all(10.0),
child: FlatButton(
onPressed: () {
_f1();
},
color: Colors.black12,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0)
),
child: Text(
'ok',
style: TextStyle(
color: Colors.deepPurple,
fontSize: 22.0,
),
),
),
),
),
Align(
alignment: Alignment.centerLeft,
child: Container(
padding: const EdgeInsets.all(10.0),
child: Text(
_str,
style: TextStyle(
color: Colors.red,
fontSize: 36.0,
),
),
),
),
],
);
}
}
To output a command every second, try this example function:
void countSeconds(s) {
for( var i = 1 ; i <= s; i++ ) {
Future.delayed(Duration(seconds: i), () => print(i));
}
}
Extracted from here.
Adapting to your case, we can have:
void _f1() {
setState(() {
//clear output
_str = '';
});
for(int i=1; i<=5; i++) {
Future.delayed(Duration(seconds: i), () {
setState(() {
_str += '$i ';
});
});
}
}
The key difference here is that you were setting 5 future.delayed at the same time (Ok, maybe a few milliseconds from each other) so they all ended up at roughly the same time. So the fix is to set from the start futures to every desired duration, and not to try to set "concatenated" futures.
You can achieve this with Timer.periodic
class OutputOneByOne extends StatefulWidget {
#override
_OutputOneByOneState createState() => _OutputOneByOneState();
}
class _OutputOneByOneState extends State<OutputOneByOne> {
int counter = 1;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Center(
child: Text("$counter"),
),
),
floatingActionButton: FloatingActionButton(
onPressed: startTimer,
),
);
}
void startTimer(){
Timer.periodic(Duration(seconds: 1), (timer) {
setState(() {
counter++;
if(counter == 5){
timer.cancel();
}
});
});
}
}
Hope this helps you.
How can I create this sticky buy button animation of Adidas app in Flutter. I have tried to use a scroll controller to listen for the position of user and then use an animated container but it is of no use since I have to define my scroll controller in my initstate while the height of my containers are relative to my device's height.
here is the link of video for the animation:
https://drive.google.com/file/d/1TzIUBr6abRQI87xAVu4NOPG67aftzceK/view?usp=sharing
this is what the widget tree looks like:
Scaffold(appbar,FAB,_body),
_body= SingleChildSrollView child:Column[Container(child:Listview)
,Container(child:PageView(children:[GridView])
,Container
,Container(this is where the shop button should be, the one that replaces the FAB)
,GridView,])
Output:
void main() => runApp(MaterialApp(home: Scaffold(body: HomePage(), appBar: AppBar())));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
ScrollController _controller = ScrollController();
double _boxHeight = 200, _screenHeight;
int _itemIndex = 5;
bool _itemVisibility = true;
#override
void initState() {
super.initState();
double offsetEnd;
WidgetsBinding.instance.addPostFrameCallback((_) {
RenderBox box = context.findRenderObject();
_screenHeight = box.globalToLocal(Offset(0, MediaQuery.of(context).size.height)).dy;
offsetEnd = ((_itemIndex + 1) - (_screenHeight / _boxHeight)) * _boxHeight;
});
_controller.addListener(() {
if (_controller.position.pixels >= offsetEnd) {
if (_itemVisibility) setState(() => _itemVisibility = false);
} else {
if (!_itemVisibility) setState(() => _itemVisibility = true);
}
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView.builder(
controller: _controller,
itemCount: 8,
itemBuilder: (context, index) {
return _buildBox(
index: index,
color: index == _itemIndex ? Colors.cyan : Colors.blue[((index + 1) * 100) % 900],
);
},
),
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Visibility(
visible: _itemVisibility,
child: _buildBox(index: _itemIndex, color: Colors.cyan),
),
),
],
);
}
Widget _buildBox({int index, Color color}) {
return Container(
height: _boxHeight,
color: color,
alignment: Alignment.center,
child: Text(
"${index}",
style: TextStyle(fontSize: 52, fontWeight: FontWeight.bold),
),
);
}
}
Another answer (variable height boxes)
void main() => runApp(MaterialApp(home: Scaffold(body: HomePage(), appBar: AppBar())));
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> with WidgetsBindingObserver {
ScrollController _controller = ScrollController();
double _screenHeight, _hRatings = 350, _hSize = 120, _hWidth = 130, _hComfort = 140, _hQuality = 150, _hBuy = 130, _hQuestions = 400;
bool _itemVisibility = true;
#override
void initState() {
super.initState();
double offsetEnd;
WidgetsBinding.instance.addPostFrameCallback((_) {
RenderBox box = context.findRenderObject();
_screenHeight = box.globalToLocal(Offset(0, MediaQuery.of(context).size.height)).dy;
offsetEnd = (_hRatings + _hSize + _hWidth + _hComfort + _hQuality + _hBuy) - _screenHeight;
});
_controller.addListener(() {
if (_controller.position.pixels >= offsetEnd) {
if (_itemVisibility) setState(() => _itemVisibility = false);
} else {
if (!_itemVisibility) setState(() => _itemVisibility = true);
}
});
}
#override
Widget build(BuildContext context) {
return Stack(
children: <Widget>[
ListView(
controller: _controller,
children: <Widget>[
_buildBox(_hRatings, "Ratings box", Colors.blue[200]),
_buildBox(_hSize, "Size box", Colors.blue[300]),
_buildBox(_hWidth, "Width box", Colors.blue[400]),
_buildBox(_hComfort, "Comfort box", Colors.blue[500]),
_buildBox(_hQuality, "Quality box", Colors.blue[600]),
_buildBox(_hBuy, "Buy box", Colors.orange[700]),
_buildBox(_hQuestions, "Questions part", Colors.blue[800]),
],
),
Positioned(
bottom: 0,
right: 0,
left: 0,
child: Visibility(
visible: _itemVisibility,
child: _buildBox(_hBuy, "Buy box", Colors.orange[700]),
),
),
],
);
}
Widget _buildBox(double height, String text, Color color) {
return Container(
height: height,
color: color,
alignment: Alignment.center,
child: Text(
text,
style: TextStyle(
fontSize: 32,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
);
}
}
I would show a floatingActionButton when the embedded button is not visible. Here's a solution based on this thread : How to know if a widget is visible within a viewport?
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() => new MyAppState();
}
class MyAppState extends State<MyApp> {
GlobalKey<State> key = new GlobalKey();
double fabOpacity = 1.0;
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: new Text("Scrolling."),
),
body: NotificationListener<ScrollNotification>(
child: new ListView(
itemExtent: 100.0,
children: [
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
new MyObservableWidget(key: key),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder(),
ContainerWithBorder()
],
),
onNotification: (ScrollNotification scroll) {
var currentContext = key.currentContext;
if (currentContext == null) return false;
var renderObject = currentContext.findRenderObject();
RenderAbstractViewport viewport = RenderAbstractViewport.of(renderObject);
var offsetToRevealBottom = viewport.getOffsetToReveal(renderObject, 1.0);
var offsetToRevealTop = viewport.getOffsetToReveal(renderObject, 0.0);
if (offsetToRevealBottom.offset > scroll.metrics.pixels ||
scroll.metrics.pixels > offsetToRevealTop.offset) {
if (fabOpacity != 1.0) {
setState(() {
fabOpacity = 1.0;
});
}
} else {
if (fabOpacity == 1.0) {
setState(() {
fabOpacity = 0.0;
});
}
}
return false;
},
),
floatingActionButton: new Opacity(
opacity: fabOpacity,
child: Align(
alignment: Alignment.bottomCenter,
child: new FloatingActionButton.extended(
label: Text('sticky buy button'),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(4.0)),
onPressed: () {
print("YAY");
},
),
),
),
),
);
}
}
class MyObservableWidget extends StatefulWidget {
const MyObservableWidget({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => new MyObservableWidgetState();
}
class MyObservableWidgetState extends State<MyObservableWidget> {
#override
Widget build(BuildContext context) {
return new RaisedButton(
onPressed: () {
},
color: Colors.lightGreenAccent,
child: Text('This is my buy button', style: TextStyle(color: Colors.blue),),
);
}
}
class ContainerWithBorder extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Container(
decoration: new BoxDecoration(border: new Border.all(), color: Colors.grey),
);
}
}
In the following Flutter app, I'm trying to show a LinearProgressIndicator in each card only when that card is counting. The the correct progression is printed to the console, but I can't figure out how to access "stepProgress" variable from the LinearProgressIndicator widget to update the view.
The cards are being built with a builder because they will change based on the input List (Array) of Maps (Objects).
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
final key = new GlobalKey<_MyHomePageState>();
List<Widget> cards = [];
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Title',
theme: ThemeData(
primarySwatch: Colors.blue,
canvasColor: Colors.grey[350],
),
home: MyHomePage(title: 'Title', key: key),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List _sequence = [];
double stepProgress = 0.0;
#override
initState() {
super.initState();
setState(() => _sequence = [
{'iterations': 1, 'time': 10},
{'iterations': 3, 'time': 7},
{'iterations': 2, 'time': 5},
]);
setState(() {
cards = getRun();
});
_countdown(_sequence, null);
}
getRun() {
List<Widget> runCards = [];
for (var group in _sequence) {
runCards.add(_buildCard(CardModel(
iterationsInGroup: group['iterations'],
timeEach: group['time'],
)));
}
return runCards;
}
void _countdown(seq, iters) async {
if (seq.length > 0) {
int i = iters == null ? seq[0]['iterations'] : iters;
if (i > 0) {
int duration = seq[0]["time"];
Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick < duration) {
setState(() {
stepProgress = timer.tick / duration;
});
print('Iteration $i: ${timer.tick} / $duration = $stepProgress');
} else {
print('Finished iteration $i');
timer.cancel();
i = i - 1;
if (i > 0) {
_countdown(seq, i); // Next iteration
} else {
print('Finished group ${seq.length}');
timer.cancel();
if (seq.length > 1) {
_countdown(seq.sublist(1), null); // Next group
} else {
print('Done');
}
}
}
});
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
'Header',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: ListView(
children: cards,
padding: const EdgeInsets.all(8.0),
),
),
],
),
);
}
}
Widget _buildCard(CardModel card) {
List<Widget> columnData = <Widget>[];
columnData.add(
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
card.timeEach.toString() +
' seconds ' +
card.iterationsInGroup.toString() +
' times',
style: TextStyle(fontSize: 22.0),
),
),
true //key.currentState.activeStep == card.cardStep //TODO: This doesn't work
? LinearProgressIndicator(
value: key.currentState.stepProgress,
)
: Container(width: 0.0, height: 0.0),
],
),
);
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Column(children: columnData),
),
);
}
class CardModel {
final int iterationsInGroup;
final int timeEach;
CardModel({
this.iterationsInGroup,
this.timeEach,
});
}
I modified your code a little, this is a little messy therefore I recommend you the following:
Create a StatefulWidget for your ChildView (Like my code below).
Keep the progress logic into the ChildView, it would be easy to change the status in that way.
I had to keep track the Globalkey in order to refresh the changes into the child view, but if you handle the logic into each child, you don't need the GlobalKey.
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
final key = new GlobalKey<_MyHomePageState>();
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Title',
theme: ThemeData(
primarySwatch: Colors.blue,
canvasColor: Colors.grey[350],
),
home: MyHomePage(title: 'Title', key: key),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
State createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List _sequence = [];
List<ChildView> runCards = [];
#override
initState() {
super.initState();
setState(() => _sequence = [
{'iterations': 1, 'time': 10, 'progress': 0.0},
{'iterations': 3, 'time': 7, 'progress': 0.0},
{'iterations': 2, 'time': 5, 'progress': 0.0},
]);
getRun();
_countdown(_sequence, null);
}
getRun() {
for (var group in _sequence) {
var cardModel = CardModel(
iterationsInGroup: group['iterations'],
timeEach: group['time'],
progress: group['progress'],
);
runCards.add(new ChildView(cardModel,new GlobalKey<_ChildViewState>()));
}
setState(() {
});
return runCards;
}
void _countdown(seq, iters) async {
if (seq.length > 0) {
int i = iters == null ? seq[0]['iterations'] : iters;
if (i > 0) {
int duration = seq[0]["time"];
Timer.periodic(Duration(seconds: 1), (timer) {
if (timer.tick <= duration) {
var childView = runCards[i-1];
double stepProgress = 0.0;
stepProgress = timer.tick / duration;
childView.key.currentState.updateProgress(stepProgress);
print('Iteration $i: ${timer.tick} / $duration = $stepProgress');
} else {
print('Finished iteration $i');
timer.cancel();
i = i - 1;
if (i > 0) {
_countdown(seq, i); // Next iteration
} else {
print('Finished group ${seq.length}');
timer.cancel();
if (seq.length > 1) {
_countdown(seq.sublist(1), null); // Next group
} else {
print('Done');
}
}
}
});
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(15.0),
child: Text(
'Header',
style: TextStyle(
fontSize: 24.0,
fontWeight: FontWeight.bold,
),
),
),
Expanded(
child: ListView(
children: runCards,
padding: const EdgeInsets.all(8.0),
),
),
],
),
);
}
}
class CardModel {
final int iterationsInGroup;
final int timeEach;
double progress;
CardModel({
this.iterationsInGroup,
this.timeEach,
this.progress,
});
}
class ChildView extends StatefulWidget {
final CardModel card;
final GlobalKey<_ChildViewState> key;
ChildView(this.card, this.key) : super(key: key);
#override
_ChildViewState createState() => _ChildViewState();
}
class _ChildViewState extends State<ChildView> {
void updateProgress(double progress){
setState(() {
widget.card.progress = progress;
});
}
#override
Widget build(BuildContext context) {
List<Widget> columnData = <Widget>[];
columnData.add(
Column(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
widget.card.timeEach.toString() +
' seconds ' +
widget.card.iterationsInGroup.toString() +
' times',
style: TextStyle(fontSize: 22.0),
),
),
widget.card.progress < 1 //key.currentState.activeStep == card.cardStep //TODO: This doesn't work
? LinearProgressIndicator(
value: widget.card.progress,
)
: Container( child: new Text("Completed"),),
],
),
);
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 15.0),
child: Column(children: columnData),
),
);
}
}