Updating state of widget from another widget in flutter - flutter

I have been writing a Sorting visualiser in flutter, I am so far able to animate the movement of blocks. But I also want to update the colours of the block, when the block goes through the states of being scanned, moved, and finally when it is completely sorted. I looked up the State management in flutter, and it is rather confusing to know what approach should I be using in my project. Below is the DashBoard Class:
import 'package:algolizer/sortingAlgorithms/Block.dart';
import 'package:flutter/material.dart';
import 'dart:math';
class DashBoard extends StatefulWidget {
double width;
double height;
DashBoard(#required this.width, #required this.height);
#override
_DashBoardState createState() => _DashBoardState();
}
class _DashBoardState extends State<DashBoard> {
double currentSliderValue = 50;
List<double> arr = new List(500);
List<Block> blockList;
bool running = false;
#override
void initState() {
// TODO: implement initState
super.initState();
fillArr((widget.width * 0.6) / 50, (widget.width * 0.1) / 50,
widget.height * 0.7);
}
void InsertionSort() async {
setState(() {
running = true;
});
int delay = (pow(15, 4) / pow(currentSliderValue, 2)).round();
for (int i = 1; i < currentSliderValue; i++) {
if (blockList[i] == null) break;
Block key = blockList[i];
int j = i - 1;
while (j >= 0 && blockList[j].height > key.height) {
setState(() {
blockList[j + 1] = blockList[j];
});
await Future.delayed(Duration(milliseconds: delay));
j--;
}
blockList[j + 1] = key;
}
setState(() {
running = false;
});
}
void BubbleSort() async {
setState(() {
running = true;
});
int delay = (pow(15, 4) / pow(currentSliderValue, 2)).round();
for (int i = 0; i < currentSliderValue - 1; i++) {
for (int j = 0; j < currentSliderValue - i - 1; j++) {
if (blockList[j].height > blockList[j + 1].height) {
Block temp = blockList[j + 1];
setState(() {
blockList[j + 1] = blockList[j];
blockList[j] = temp;
});
await Future.delayed(Duration(milliseconds: delay));
}
}
}
setState(() {
running = false;
});
}
// Map<String, >
void fillArr(double width, double margin, double height) {
for (int i = 0; i < arr.length; i++) arr[i] = null;
var rng = new Random();
for (int i = 0; i < currentSliderValue; i++) {
double val = rng.nextDouble() * height;
if (val == 0)
continue;
else
arr[i] = val;
}
blockList = [...arr.map((height) => Block(height, width, margin))];
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(height: 20),
Row(
children: [
Text(
"Length",
),
Slider(
value: currentSliderValue,
min: 5,
max: 200,
onChanged: (double value) {
setState(() {
currentSliderValue = value;
});
double newwidth =
(MediaQuery.of(context).size.width * 0.6) /
currentSliderValue;
double newmargin =
(MediaQuery.of(context).size.width * 0.1) /
currentSliderValue;
fillArr(newwidth, newmargin, widget.height * 0.7);
}),
RaisedButton(
child: Text("Insertion Sort"),
onPressed: InsertionSort,
),
RaisedButton(
onPressed: BubbleSort, child: Text("Bubble Sort")),
RaisedButton(onPressed: () {}, child: Text("Merge Sort")),
RaisedButton(onPressed: () {}, child: Text("Quick Sort")),
RaisedButton(
onPressed: () {}, child: Text("Counting Sort")),
RaisedButton(onPressed: () {}, child: Text("Radix Sort")),
RaisedButton(
onPressed: () {}, child: Text("Selection Sort")),
RaisedButton(onPressed: () {}, child: Text("Heap Sort")),
],
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [...blockList],
),
// Row(
// children: [
// Container(
// child: Row(children: [
// Text("Algorithm")
// ],)
// )]
// ),
],
),
),
);
}
}
Here's the Block class:
import 'package:flutter/material.dart';
class Block extends StatefulWidget {
Block(#required this.height, #required this.width, #required this.mar);
double height;
double width;
double mar;
#override
_BlockState createState() => _BlockState();
}
class _BlockState extends State<Block> {
Color col = Colors.blue;
// void isKey() {
// setState(() {
// col = Colors.pink;
// });
// }
// void notKey() {
// setState(() {
// col = Colors.purple;
// });
// }
#override
Widget build(BuildContext context) {
return (widget.height == null)
? Container()
: Container(
height: this.widget.height,
width: widget.width,
margin: EdgeInsets.all(widget.mar),
decoration: BoxDecoration(
color: col,
),
);
}
}

As far as which state management route to go with, it really can be done with any of them. GetX to me is the easiest and has the least boilerplate.
Here's one way to do this. I just updated the insertionSort method to get you started and you can go from there. Any other changes you notice in your other classes are just to get rid of linter errors.
All your methods and variables can now live in a GetX class. With the exception of color, the rest are now observable streams.
class BlockController extends GetxController {
RxDouble currentSliderValue = 50.0.obs; // adding .obs makes variable obserable
RxList arr = List(500).obs;
RxList blockList = [].obs;
RxBool running = false.obs;
Color color = Colors.red;
void insertionSort() async {
running.value = true; // adding .value access the value of observable variable
color = Colors.blue;
int delay = (pow(15, 4) / pow(currentSliderValue.value, 2)).round();
for (int i = 1; i < currentSliderValue.value; i++) {
if (blockList[i] == null) break;
Block key = blockList[i];
int j = i - 1;
while (j >= 0 && blockList[j].height > key.height) {
blockList[j + 1] = blockList[j];
await Future.delayed(Duration(milliseconds: delay));
j--;
}
blockList[j + 1] = key;
}
color = Colors.green;
update(); // only needed for the color property because its not an observable stream
running.value = false;
}
void bubbleSort() async {
running.value = true;
int delay = (pow(15, 4) / pow(currentSliderValue.value, 2)).round();
for (int i = 0; i < currentSliderValue.value - 1; i++) {
for (int j = 0; j < currentSliderValue.value - i - 1; j++) {
if (blockList[j].height > blockList[j + 1].height) {
Block temp = blockList[j + 1];
blockList[j + 1] = blockList[j];
blockList[j] = temp;
await Future.delayed(Duration(milliseconds: delay));
}
}
}
running.value = false;
}
// Map<String, >
void fillArr(double width, double margin, double height) {
for (int i = 0; i < arr.length; i++) arr[i] = null;
var rng = new Random();
for (int i = 0; i < currentSliderValue.value; i++) {
double val = rng.nextDouble() * height;
if (val == 0)
continue;
else
arr[i] = val;
}
blockList = [...arr.map((height) => Block(height, width, margin))].obs;
}
}
Initialize the controller in your main before running your app. Generally it can be done anywhere as long as its before you try to access the controller.
Get.put(BlockController());
Here's your much less busy DashBoard now that all that logic is tucked away in a GetX class. Here we find the controller, and use it access all those variables and methods.
Obx is the GetX widget that rebuilds on changes.
class DashBoard extends StatefulWidget {
final double width;
final double height;
DashBoard(this.width, this.height);
#override
_DashBoardState createState() => _DashBoardState();
}
class _DashBoardState extends State<DashBoard> {
final controller = Get.find<BlockController>(); // finding same instance of BlockConroller that you initialized in `Main`
#override
void initState() {
super.initState();
controller.fillArr((widget.width * 0.6) / 50, (widget.width * 0.1) / 50,
widget.height * 0.7);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
children: [
SizedBox(height: 50),
Obx(
// rebuilds when observable variables change
() => Column(
// changed to Column because a Row was overflowing
children: [
Text(
"Length",
),
Slider(
value: controller.currentSliderValue.value,
min: 5,
max: 200,
onChanged: (double value) {
controller.currentSliderValue.value = value;
double newwidth =
(MediaQuery.of(context).size.width * 0.6) /
controller.currentSliderValue.value;
double newmargin =
(MediaQuery.of(context).size.width * 0.1) /
controller.currentSliderValue.value;
controller.fillArr(
newwidth, newmargin, widget.height * 0.7);
}),
RaisedButton(
child: Text("Insertion Sort"),
onPressed: controller.insertionSort,
),
RaisedButton(
onPressed: controller.bubbleSort,
child: Text("Bubble Sort")),
RaisedButton(onPressed: () {}, child: Text("Merge Sort")),
RaisedButton(onPressed: () {}, child: Text("Quick Sort")),
RaisedButton(onPressed: () {}, child: Text("Counting Sort")),
RaisedButton(onPressed: () {}, child: Text("Radix Sort")),
RaisedButton(onPressed: () {}, child: Text("Selection Sort")),
RaisedButton(onPressed: () {}, child: Text("Heap Sort")),
],
),
),
Obx(
// rebuilds when observable variables change
() => Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [...controller.blockList],
),
),
// Row(
// children: [
// Container(
// child: Row(children: [
// Text("Algorithm")
// ],)
// )]
// ),
],
),
),
);
}
}
And here's your Block which can now be stateless. Key thing of note here is the GetBuilder widget that updates the color.
class Block extends StatelessWidget {
// now can be stateless
Block(this.height, this.width, this.mar);
final double height;
final double width;
final double mar;
#override
Widget build(BuildContext context) {
return (height == null)
? Container()
: GetBuilder<BlockController>(
// triggers rebuilds when update() is called from GetX class
builder: (controller) => Container(
height: this.height,
width: width,
margin: EdgeInsets.all(mar),
decoration: BoxDecoration(
color: controller.color,
),
),
);
}
}

Related

jumping speed keeps increasing in flutter

I was building a flutter project ( a small game) where my container(player) can jump when barriers came to it I was able to make a jump using the code below but every time pressed jump it keeps getting faster and faster I don't think the issue is with variables I can't figure out what is happening (there were no errors in the code)
the jump part of code is made bold to make it easy to find for you
import 'dart:async';
import 'package:Buckly/extensions/player.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:Buckly/extensions/neu_box.dart';
import 'extensions/Arrow_button.dart';
class lvl_one extends StatefulWidget {
const lvl_one({Key key}) : super(key: key);
#override
State<lvl_one> createState() => _lvl_oneState();
}
class _lvl_oneState extends State<lvl_one> {
//player variable
static double positionx = 0;
static double positiony = 1;
double time = 0;
double height = 0;
double vel = 0;
int duration = 50;
double initial_height = positiony;
void moveleft() {
setState(() {
if (positionx - 0.1 < -1) {
//nothing
} else {
positionx -= 0.1;
}
});
}
void moveright() {
setState(() {
if (positionx + 0.1 > 1) {
//nothing
} else {
positionx += 0.1;
}
});
}
void prejump() {
time = 0;
positiony = 1;
height = 0;
initial_height = 1;
duration = 50;
}
**void jump() {
setState(() {
prejump();
});
Timer.periodic(Duration(milliseconds: duration), (timer) {
time += 0.05;
height = -5 * time * time + 5 * time;
if (initial_height - height > 1) {
setState(() {
positiony = 1;
});
} else {
setState(() {
positiony = initial_height - height;
print("time is ${time}");
});
}
});
setState(() {
prejump();
});
}**
#override
Widget build(BuildContext context) {
double size = MediaQuery
.of(context)
.size
.width;
return SafeArea(
child: Column(
children: [
Expanded(flex: 3,
child: Container(
width: size, color: Colors.purple,
child: Stack(
children: [
player(positionx: positionx, positiony: positiony,),
],
),
)),
Container(height: 10, color: Colors.blueGrey,),
Expanded(flex: 1,
child: Container(
width: size, color: Colors.pink,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
SizedBox(height: 50,
width: 50,
child: NeuBox(
child: Icon(Icons.arrow_left), function: moveleft,)),
SizedBox(height: 50,
width: 50,
**child: NeuBox(child: Icon(Icons.arrow_drop_up_outlined),
function: jump,)),**
SizedBox(height: 50,
width: 50,
child: NeuBox(child: Icon(Icons.arrow_right_outlined),
function: moveright,)),
],
),
)),
],
),
);
}
}
Because you use Timer.periodic(.. inside your void jump() function and it will create a new timer every time you call it.
You can try to print something like this inside the callback function:
Timer.periodic(Duration(milliseconds: duration), (timer) {
print("time is ${time}");
time += 0.05;
...
You will soon figure out that the time += 0.5 which should be call one in every 50 milliseconds become called twice (or more).
One way is move the Timer.periodic into the initial state and use prejump to jump.
#override
void initState() {
Timer.periodic(Duration(milliseconds: duration), (timer) {
print("time is ${time}");
time += 0.05;
height = -5 * time * time + 5 * time;
if (initial_height - height > 1) {
setState(() {
positiony = 1;
});
} else {
setState(() {
positiony = initial_height - height;
});
}
});
super.initState();
}
void prejump() {
time = 0;
positiony = 1;
height = 0;
initial_height = 1;
duration = 50;
}
void jump() {
setState(() {
prejump();
});
}

Flutter: How to show AppBar only after scrolling?

That's what you read. I don't want to hide AppBar when scrolling, there's a lot of info on that.
What I want is the exact opposite. I want my homepage to open with no AppBar and then, when the user starts scrolling, the appbar will be displayed.
This website does exactly what I want to reproduce: https://www.kirschnerbrasil.cc/ (in the desktop version).
I guess I need do use the SliverAppBar, but I haven't manage to do so yet. Can anyone help?
Thanks!
In that case, you will have to make your custom widget the appbar. Please have a look into below code, it will help you to understand the procedure:
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// Height of your Container
static final _containerHeight = 100.0;
// You don't need to change any of these variables
var _fromTop = -_containerHeight;
var _controller = ScrollController();
var _allowReverse = true, _allowForward = true;
var _prevOffset = 0.0;
var _prevForwardOffset = -_containerHeight;
var _prevReverseOffset = 0.0;
#override
void initState() {
super.initState();
_controller.addListener(_listener);
}
// entire logic is inside this listener for ListView
void _listener() {
double offset = _controller.offset;
var direction = _controller.position.userScrollDirection;
if (direction == ScrollDirection.reverse) {
_allowForward = true;
if (_allowReverse) {
_allowReverse = false;
_prevOffset = offset;
_prevForwardOffset = _fromTop;
}
var difference = offset - _prevOffset;
_fromTop = _prevForwardOffset + difference;
if (_fromTop > 0) _fromTop = 0;
} else if (direction == ScrollDirection.forward) {
_allowReverse = true;
if (_allowForward) {
_allowForward = false;
_prevOffset = offset;
_prevReverseOffset = _fromTop;
}
var difference = offset - _prevOffset;
_fromTop = _prevReverseOffset + difference;
if (_fromTop < -_containerHeight) _fromTop = -_containerHeight;
}
setState(() {}); // for simplicity I'm calling setState here, you can put bool values to only call setState when there is a genuine change in _fromTop
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("ListView")),
body: Stack(
children: <Widget>[
_yourListView(),
Positioned(
top: _fromTop,
left: 0,
right: 0,
child: _yourContainer(),
)
],
),
);
}
Widget _yourListView() {
return ListView.builder(
itemCount: 100,
controller: _controller,
itemBuilder: (_, index) => ListTile(title: Text("Item $index")),
);
}
Widget _yourContainer() {
return Opacity(
opacity: 1 - (-_fromTop / _containerHeight),
child: Container(
height: _containerHeight,
color: Colors.red,
alignment: Alignment.center,
child: Text("Your Container", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white)),
),
);
}
}
Heres the code i edit from #Gourango Sutradhar answer, if you want the top to disappear only when it reach the top
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
// Height of your Container
static final _containerHeight = 100.0;
// You don't need to change any of these variables
var _fromTop = -_containerHeight;
var _controller = ScrollController();
var _allowReverse = true, _allowForward = true;
var _prevOffset = 0.0;
var _prevForwardOffset = -_containerHeight;
var _prevReverseOffset = 0.0;
#override
void initState() {
super.initState();
_controller.addListener(_listener);
}
// entire logic is inside this listener for ListView
void _listener() {
double offset = _controller.offset;
var direction = _controller.position.userScrollDirection;
if (direction == ScrollDirection.reverse) {
_allowForward = true;
if (_allowReverse) {
_allowReverse = false;
_prevOffset = offset;
_prevForwardOffset = _fromTop;
}
var difference = offset - _prevOffset;
_fromTop = _prevForwardOffset + difference;
if (_fromTop > 0) _fromTop = 0;
} else if (direction == ScrollDirection.forward) {
_allowReverse = true;
if (_allowForward) {
_allowForward = false;
_prevReverseOffset = _fromTop;
}
var difference = offset - _prevOffset;
if (offset > 100.0) {
_prevOffset = offset;
}
if (offset < 100.0) {
_fromTop = _prevReverseOffset + difference;
if (_fromTop < -_containerHeight) _fromTop = -_containerHeight;
}
}
setState(() {});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("ListView")),
body: Stack(
children: <Widget>[
_yourListView(),
Positioned(
top: _fromTop,
left: 0,
right: 0,
child: _yourContainer(),
)
],
),
);
}
Widget _yourListView() {
return ListView.builder(
itemCount: 100,
controller: _controller,
itemBuilder: (_, index) => ListTile(title: Text("Item $index")),
);
}
Widget _yourContainer() {
return Opacity(
opacity: 1,
child: Container(
height: _containerHeight,
color: Colors.red,
alignment: Alignment.center,
child: Text("Your Container", style: TextStyle(fontSize: 32, fontWeight: FontWeight.bold, color: Colors.white)),
),
);
}
}

The method 'findRenderObject' was called on null - With StackTrace

Hi I'm new to Flutter Dart.
In my project I get the excpetion: The method 'findRenderObject' was called on null. Receiver: null Tried calling: findRenderObject()
This happens when I'm firing the ElevatedButton in my main.dart file.
The function that will be fired is the startGame function in my play_field.dart file.
Anyone an idea why I get this exception when firing this button?
The StackTrace:
#0 Object.noSuchMethod (dart:core-patch/object_patch.dart:54:5)
#1 PlayFieldState.startGame
package:woost_games/…/game/play_field.dart:74
#2 _TetrisState.build.<anonymous closure>
package:woost_games/main.dart:88
#3 _InkResponseState._handleTap
package:flutter/…/material/ink_well.dart:991
#4 GestureRecognizer.invokeCallback
package:flutter/…/gestures/recognizer.dart:182
...
Handler: "onTap"
Recognizer: TapGestureRecognizer#dbf10
debugOwner: GestureDetector
state: possible
won arena
finalPosition: Offset(332.0, 284.3)
finalLocalPosition: Offset(33.1, 7.9)
button: 1
sent tap down
See my code below:
Main.dart
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
import 'widgets/block/block.dart';
import 'widgets/block/next_block.dart';
import 'widgets/game/play_field.dart';
import 'widgets/game/score_bar.dart';
void main() => runApp(
ChangeNotifierProvider(
create: (context) => Data(),
child: WoostGames()
)
);
class WoostGames extends StatelessWidget {
#override
Widget build(BuildContext context) {
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return MaterialApp(home: Tetris());
}
}
class Tetris extends StatefulWidget {
#override
State<StatefulWidget> createState() => _TetrisState();
}
class _TetrisState extends State<Tetris> {
GlobalKey<PlayFieldState> _playFieldKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(
'Woost Tetris',
style: TextStyle(
color: Colors.black,
fontFamily: 'Neue Montreal',
fontSize: 25,
fontWeight: FontWeight.w700
)
),
centerTitle: true,
backgroundColor: Colors.yellow,
),
backgroundColor: Colors.yellow,
body: SafeArea(
child: Column(children: <Widget>[
ScoreBar(),
Expanded(
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Flexible(
flex: 3,
child: Padding(
padding: EdgeInsets.fromLTRB(5, 0, 2.5, 5),
child: PlayField(key: _playFieldKey),
)
),
Flexible(
child: Padding(
padding: EdgeInsets.fromLTRB(2.5, 0, 5, 5),
child: Column(
children: <Widget>[
NextBlock(),
SizedBox(height: 30),
ElevatedButton(
child: Text(
Provider.of<Data>(context, listen: false).inGame
? 'End'
: 'Start',
style: TextStyle(
fontSize: 18,
color: Colors.yellow
)
),
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all<Color>(Colors.black)
),
onPressed: () {
Provider.of<Data>(context, listen: false).inGame
? _playFieldKey.currentState.endGame()
: _playFieldKey.currentState.startGame();
}
)
]
)
)
),
],
),
)
])
)
);
}
}
class Data with ChangeNotifier {
int score = 0;
bool inGame = false;
Block nextBlock;
void setScore(score) {
this.score = score ;
notifyListeners();
}
void addScore(score) {
this.score += score;
notifyListeners();
}
void setInGame(inGame) {
this.inGame = inGame;
notifyListeners();
}
void setNextBlock(Block nextBlock) {
this.nextBlock = nextBlock;
notifyListeners();
}
Widget getNextBlockWidget() {
if (!inGame) return Container();
var width = nextBlock.width;
var height = nextBlock.height;
var color;
List<Widget> columns = [];
for (var y = 0; y < height; y++) {
List<Widget> rows = [];
for (var x = 0; x < width; ++ x) {
color = (nextBlock.subBlocks
.where((subBlock) => subBlock.x == x && subBlock.y == y)
.length > 0) ? nextBlock.color : Colors.transparent;
rows.add(Container(width: 12, height: 12, color: color));
}
columns.add(Row(
mainAxisAlignment: MainAxisAlignment.center,
children: rows
));
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: columns,
);
}
}
play_field.dart
import 'dart:math';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../../main.dart';
import '../block/block.dart';
import '../block/blocks.dart';
import '../block/sub_block.dart';
enum Collision {
LANDED_ON_BOTTOM,
LANDED_ON_BLOCK,
HIT_WALL,
HIT_BLOCK,
NONE
}
const PLAY_FIELD_WIDTH = 10;
const PLAY_FIELD_HEIGHT = 20;
const REFRESH_RATE = 300;
const GAME_AREA_BORDER_WIDTH = 2.0;
const SUB_BLOCK_EDGE_WIDTH = 2.0;
class PlayField extends StatefulWidget {
PlayField({Key key}) : super(key: key);
#override
State<StatefulWidget> createState() => PlayFieldState();
}
class PlayFieldState extends State<PlayField> {
bool isGameOver = false;
double subBlockWidth;
Duration blockMovementDuration = Duration(milliseconds: REFRESH_RATE);
GlobalKey _playFieldAreaKey = GlobalKey();
BlockMovement action;
Block block;
Timer timer;
List<SubBlock> oldSubBlocks;
Block getNewBlock() {
int blockType = Random().nextInt(7);
int orientationIndex = Random().nextInt(4);
switch (blockType) {
case 0:
return IBlock(orientationIndex);
case 1:
return JBlock(orientationIndex);
case 2:
return LBlock(orientationIndex);
case 3:
return OBlock(orientationIndex);
case 4:
return TBlock(orientationIndex);
case 5:
return SBlock(orientationIndex);
case 6:
return ZBlock(orientationIndex);
default:
return null;
}
}
void startGame() {
Provider.of<Data>(context, listen: false).setInGame(true);
Provider.of<Data>(context, listen: false).setScore(0);
oldSubBlocks = [];
RenderBox renderBoxPlayField = _playFieldAreaKey.currentContext.findRenderObject();
subBlockWidth = (renderBoxPlayField.size.width - GAME_AREA_BORDER_WIDTH * 2) / PLAY_FIELD_WIDTH;
Provider.of<Data>(context, listen: false).setNextBlock(getNewBlock());
block = getNewBlock();
timer = Timer.periodic(blockMovementDuration, onPlay);
}
void endGame() {
Provider.of<Data>(context, listen: false).setInGame(false);
timer.cancel();
}
void onPlay(Timer timer) {
var status = Collision.NONE;
setState(() {
if (action != null) {
if (!checkOnEdge(action)) {
block.move(action);
}
}
oldSubBlocks.forEach((oldSubBlock) {
block.subBlocks.forEach((subBlock) {
var x = block.x + subBlock.x;
var y = block.y + subBlock.y;
if (x == oldSubBlock.x && y == oldSubBlock.y) {
switch (action) {
case BlockMovement.LEFT:
block.move(BlockMovement.RIGHT);
break;
case BlockMovement.RIGHT:
block.move(BlockMovement.LEFT);
break;
case BlockMovement.ROTATE_CLOCKWISE:
block.move(BlockMovement.ROTATE_COUNTER_CLOCKWISE);
break;
default:
break;
}
}
});
});
if (!checkAtBottom()) {
if (!checkOnBlock()) {
block.move(BlockMovement.DOWN);
} else {
status = Collision.LANDED_ON_BLOCK;
}
} else {
status = Collision.LANDED_ON_BOTTOM;
}
if (status == Collision.LANDED_ON_BLOCK && block.y < 0) {
isGameOver = true;
endGame();
}
else if (status == Collision.LANDED_ON_BOTTOM
|| status == Collision.LANDED_ON_BLOCK) {
block.subBlocks.forEach((subBlock) {
subBlock.x += block.x;
subBlock.y += block.y;
oldSubBlocks.add(subBlock);
});
}
block = Provider.of<Data>(context, listen: false).nextBlock;
Provider.of<Data>(context, listen: false).setNextBlock(getNewBlock());
});
action = null;
updateScore();
}
void updateScore() {
var combo = 1;
Map<int, int> rows = Map();
List<int> rowsToBeRemoved = [];
oldSubBlocks?.forEach((oldSubBlock) {
rows.update(oldSubBlock.y, (value) => ++value, ifAbsent: () => 1);
});
rows.forEach((rowNum, count) {
if (count == PLAY_FIELD_WIDTH) {
Provider.of<Data>(context, listen: false).setScore(combo++);
rowsToBeRemoved.add(rowNum);
}
});
if (rowsToBeRemoved.length > 0)
removeRows(rowsToBeRemoved);
}
void removeRows(List<int> rows) {
rows.sort();
rows.forEach((row) {
oldSubBlocks.removeWhere((oldSubBlock) => oldSubBlock.y == row);
oldSubBlocks.forEach((oldSubBlock) {
if (oldSubBlock.y < row)
++oldSubBlock.y;
});
});
}
bool checkAtBottom() {
return block.y + block.height == PLAY_FIELD_HEIGHT;
}
bool checkOnBlock() {
oldSubBlocks.forEach((oldSubBlock) {
block.subBlocks.forEach((subBlock) {
var x = block.x + subBlock.x;
var y = block.y + subBlock.y;
if (x == oldSubBlock.x && y + 1 == oldSubBlock.y)
return true;
});
});
return false;
}
bool checkOnEdge(BlockMovement action) {
return (action == BlockMovement.LEFT && block.x <= 0) ||
(action == BlockMovement.RIGHT && block.x + block.width >= PLAY_FIELD_WIDTH);
}
Widget getPositionedSquareContainer(Color color, int x, int y) {
return Positioned(
left: x * subBlockWidth,
top: y * subBlockWidth,
child: Container(
width: subBlockWidth - SUB_BLOCK_EDGE_WIDTH,
height: subBlockWidth - SUB_BLOCK_EDGE_WIDTH,
decoration: BoxDecoration(
color: color,
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(2.5))
)
)
);
}
Widget drawBlock() {
if (block == null) return null;
List<Positioned> subBlocks = [];
block.subBlocks.forEch((subBlock) {
subBlocks.add(getPositionedSquareContainer(
subBlock.color, subBlock.x + block.x, subBlock.y + block.y
));
});
oldSubBlocks?.forEach((oldSubBlock) {
subBlocks.add(getPositionedSquareContainer(
oldSubBlock.color, oldSubBlock.x, oldSubBlock.y
));
});
if (isGameOver) {
subBlocks.add(getGameOverRect());
}
return Stack(children: subBlocks);
}
Widget getGameOverRect() {
return Positioned(
child: Container(
width: subBlockWidth * 8,
height: subBlockWidth * 3,
alignment: Alignment.center,
decoration: BoxDecoration(
borderRadius: BorderRadius.all(Radius.circular(5)),
color: Colors.black
),
child: Text(
'Game Over',
style: TextStyle(
fontSize: 30,
fontWeight: FontWeight.bold,
color: Colors.yellow
)
)
),
left: subBlockWidth * 1,
top: subBlockWidth * 6
);
}
#override
Widget build(BuildContext context) {
return GestureDetector(
onHorizontalDragUpdate: (event) {
action = (event.delta.dx < 1)
? BlockMovement.LEFT
: BlockMovement.RIGHT;
},
onTap: () {
action = BlockMovement.ROTATE_CLOCKWISE;
},
child: AspectRatio(
aspectRatio: PLAY_FIELD_WIDTH / PLAY_FIELD_HEIGHT,
child: Container(
decoration: BoxDecoration(
color: Colors.yellow,
border: Border.all(
width: GAME_AREA_BORDER_WIDTH,
color: Colors.black
),
borderRadius: BorderRadius.all(Radius.circular(2.5))
),
),
)
);
}
}
=== EDIT ====
Screenshot of full exception:

flutter animated image is so laggy

import 'dart:math';
import 'package:audioplayers/audio_cache.dart';
import 'package:audioplayers/audioplayers.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:image_sequence_animator/image_sequence_animator.dart';
import 'package:kane/model/KaneType.dart';
class Kane extends StatefulWidget {
Kane({Key key}) : super(key: key);
#override
KaneState createState() => KaneState();
}
class KaneState extends State<Kane> {
int _noseCount = 0;
bool _isHover = false;
bool _isPlaying = false;
KaneType _kaneType = KaneType.Kane;
List<GlobalKey<ImageSequenceAnimatorState>> animatorKeyList = [
GlobalKey(),
GlobalKey(),
GlobalKey(),
GlobalKey(),
GlobalKey()
];
AudioCache _cache = AudioCache();
AudioPlayer _player;
#override
Widget build(BuildContext context) {
ScreenUtil.init(context);
Widget kane;
switch (_kaneType) {
case KaneType.Kane:
kane = _kaneAnimation("kane", 8, 15, animatorKeyList[0], true);
break;
case KaneType.Ricardo:
kane = _kaneAnimation("ricardo", 212, 15, animatorKeyList[1], false);
break;
case KaneType.SexyKane:
kane = _kaneAnimation("sexyKane", 9, 15, animatorKeyList[2], true);
break;
case KaneType.MoemoeKane:
kane = _kaneAnimation("moemoe", 137, 24, animatorKeyList[3], false);
break;
default:
kane = _kaneAnimation("hanwha", 203, 24, animatorKeyList[4], false);
}
return Stack(
children: <Widget>[
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: ScreenUtil().setHeight(800),
child: kane,
),
),
_kaneType == KaneType.Kane && !_isPlaying
? Positioned(
top: ScreenUtil().setHeight(918),
left: ScreenUtil().setWidth(506),
right: ScreenUtil().setWidth(506),
child: InkWell(
child: _isHover
? ColorFiltered(
colorFilter: ColorFilter.mode(
Colors.grey[400], BlendMode.modulate),
child: Image.asset(
"assets/kane/kane/nose.webp",
width: ScreenUtil().setHeight(40),
height: ScreenUtil().setHeight(40),
fit: BoxFit.contain,
))
: Image.asset(
"assets/kane/kane/nose.webp",
width: ScreenUtil().setHeight(40),
height: ScreenUtil().setHeight(40),
fit: BoxFit.contain,
),
onTap: () {
setState(() => _isHover = false);
if (++_noseCount >= Random().nextInt(5) + 3) {
_noseCount = 0;
_cache.play('music/igonan.m4a');
} else {
_cache.play('music/bbolong.mp3');
}
},
onTapDown: (_) => setState(() => _isHover = true),
onTapCancel: () => setState(() => _isHover = false),
),
)
: Container()
],
);
}
Widget _kaneAnimation(String name, double frameCount, double fps,
GlobalKey<ImageSequenceAnimatorState> key, bool rewind) {
bool first = true;
return InkWell(
child: ImageSequenceAnimator(
"assets/kane/$name",
"$name",
0,
frameCount.toString().length - 2,
"webp",
frameCount,
key: key,
fps: fps,
isAutoPlay: false,
color: null,
onFinishPlaying: (animator) {
if (rewind && first) {
key.currentState.rewind();
first = false;
} else {
setState(() {
_isPlaying = false;
first = true;
});
key.currentState.reset();
}
},
),
onTap: () async {
if (!_isPlaying) {
setState(() {
_isPlaying = true;
});
_player = await _cache.play('music/$name.mp3');
key.currentState.play();
} else {
setState(() {
_isPlaying = false;
first = true;
});
_player.stop();
key.currentState.reset();
}
},
);
}
changeKane(KaneType kaneType) {
setState(() => _kaneType = kaneType);
}
}
I made an animation, but some devices are lagging.
https://www.youtube.com/watch?v=oScBgRUp1B8&feature=youtu.be
The animation is lagging too much.
InkWell(
child: Image.asset(
"assets/kane/moemoe/moemoe$_imageCount.webp",
),
onTap: () async {
for (int j = 1; j <= 136; j++) {
await Future.delayed(const Duration(milliseconds: 42), () {
setState(() => _imageCount++);
});
}
},
)
It's lagging even in this way. Also lag seems to only appear on some smartphones such as Galaxy a30, s7, and s9. p.s I am not good at English.
enter image description here The total size of the image is 3mb.
You are calling setState(() => _imageCount++); inside a for loop. Be careful when using this method! Calling setState, according to the documentation:
Calling setState notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a build for this State object.
The means, this for loop:
for (int j = 1; j <= 136; j++) {
await Future.delayed(const Duration(milliseconds: 42), () {
setState(() => _imageCount++);
});
}
Might be the cause of the lag. Every loop, you are telling your application that something has changed, and it needs to rebuild all the elements inside the build() function. And that might not be the only cause of lag, as I see you use setState((){}) a lot.

Cannot set state outside a Container in Provider

I understand that the problem is in a lifecircle that I'm trying to set a state in Provider before the Widget is rendered but where can I do that. Only in a Container Widget? But I cannot do that unless I've a button or something.
I hope you got the issue of the problem here.
I would appreciate any hints!
my Error:
setState() or markNeedsBuild() called during build.
or
The setter 'lastPage=' was called on null.
Receiver: null
Tried calling: lastPage=true
if I set the state in here
_detectLastPage() {
int currentPage = this.currentStep == null ? 1 : this.currentStep + 1;
if (currentPage == 1 && this.currentStep == null) {
this._onFirstPage();
} else if (currentPage == this.totalSteps) {
this.lastPage = true;
_welcomeBloc.lastPage = true;
this._onLastPage();
} else {
this.lastPage = false;
_welcomeBloc.lastPage = true;
}
}
My Widget:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
class Footer extends StatelessWidget {
final int currentStep;
final int totalSteps;
final Color activeColor;
final Color inactiveColor;
final Duration duration;
final Function onFinal;
final Function onStart;
final double radius = 10.0;
final double distance = 4.0;
Container stepper;
Container nextArrow;
bool lastPage;
WelcomeBloc _welcomeBloc;
Footer({
this.activeColor,
this.inactiveColor,
this.currentStep,
this.totalSteps,
this.duration,
this.onFinal,
this.onStart,
}) {
this._detectLastPage();
this._makeStepper();
this._makeNextArrow();
}
#override
Widget build(BuildContext context) {
print('footer is launching');
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
// this._welcomeBloc.lastPage = true; // I'd like to set the state here
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
this.stepper,
this.nextArrow,
RaisedButton(
child: Text('kdfljds'),
onPressed: () {
print(_welcomeBloc.lastPage);
_welcomeBloc.lastPage = true; // I can access from here BUT CANNOT access outside this container
},
)
],
),
);
}
_makeCirle(activeColor, inactiveColor, position, currentStep) {
currentStep = currentStep ?? 0;
Color color = (position == currentStep) ? activeColor : inactiveColor;
return Container(
height: this.radius,
width: this.radius,
margin: EdgeInsets.only(left: this.distance, right: this.distance),
decoration: BoxDecoration(
color: color,
border: Border.all(color: activeColor, width: 2.0),
borderRadius: BorderRadius.circular(50.0)),
);
}
_makeStepper() {
List<Container> circles = List();
for (var i = 0; i < totalSteps; i++) {
circles.add(
_makeCirle(this.activeColor, this.inactiveColor, i, this.currentStep),
);
}
this.stepper = Container(
child: Row(
children: circles,
),
);
}
_makeNextArrow() {
this.nextArrow = Container(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: () {
_welcomeBloc.controller.nextPage(
duration: this.duration ?? Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Icon(
Icons.arrow_forward,
)),
),
);
}
_onLastPage() {
if (this.onFinal != null) {
this.onFinal();
}
}
_onFirstPage() {
if (this.onStart != null) {
this.onStart();
}
}
_detectLastPage() {
int currentPage = this.currentStep == null ? 1 : this.currentStep + 1;
if (currentPage == 1 && this.currentStep == null) {
this._onFirstPage();
} else if (currentPage == this.totalSteps) {
this.lastPage = true;
this._onLastPage();
} else {
this.lastPage = false;
}
}
}
BlocFile
import 'package:flutter/material.dart';
class WelcomeBloc extends ChangeNotifier {
PageController _controller = PageController();
int _currentPage;
bool _lastPage = false;
bool get lastPage => _lastPage;
set lastPage(bool value){
print(value);
_lastPage = value;
notifyListeners();
}
int get currentPage => _currentPage;
set currentPage(int value) {
_currentPage = value;
notifyListeners();
}
get controller => _controller;
nextPage(Duration duration, Curves curve){
controller.nextPage(duration: duration, curve: curve);
}
}
[![error screen with StateLess, since I use Provider][1]][1]
There I call like this:
_detectLastPage() {
int currentPage =
this.widget.currentStep == null ? 1 : this.widget.currentStep + 1;
if (currentPage == 1 && this.widget.currentStep == null) {
this._onFirstPage();
} else if (currentPage == this.widget.totalSteps) {
this.lastPage = true;
setState(() {
_welcomeBloc.lastPage = true;
});
this._onLastPage();
} else {
this.lastPage = false;
setState(() {
_welcomeBloc.lastPage = false;
});
}
}
And without SetState seem to be the same error...
this error if I call from inside initState from your example. Just forgot you attach it
You cannot use the setState method in a StatelessWidget. Convert it to a StatefulWidget and call the setState in the initState method.
Like this
class Footer extends StatefulWidget {
final int currentStep;
final int totalSteps;
final Color activeColor;
final Color inactiveColor;
final Duration duration;
final Function onFinal;
final Function onStart;
Footer({
this.activeColor,
this.inactiveColor,
this.currentStep,
this.totalSteps,
this.duration,
this.onFinal,
this.onStart,
});
#override
_FooterState createState() => _FooterState();
}
class _FooterState extends State<Footer> {
final double radius = 10.0;
final double distance = 4.0;
Container stepper;
Container nextArrow;
bool lastPage;
WelcomeBloc _welcomeBloc;
#override
void initState(){
this._detectLastPage();
this._makeStepper();
this._makeNextArrow();
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
setState((){
this._welcomeBloc.lastPage = true; // Where to use setState
});
}
#override
Widget build(BuildContext context) {
print('footer is launching');
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
this.stepper,
this.nextArrow,
RaisedButton(
child: Text('kdfljds'),
onPressed: () {
print(_welcomeBloc.lastPage);
_welcomeBloc.lastPage = true; // I can access from here BUT CANNOT access outside this container
},
)
],
),
);
}
_makeCirle(activeColor, inactiveColor, position, currentStep) {
currentStep = currentStep ?? 0;
Color color = (position == currentStep) ? activeColor : inactiveColor;
return Container(
height: this.radius,
width: this.radius,
margin: EdgeInsets.only(left: this.distance, right: this.distance),
decoration: BoxDecoration(
color: color,
border: Border.all(color: activeColor, width: 2.0),
borderRadius: BorderRadius.circular(50.0)),
);
}
_makeStepper() {
List<Container> circles = List();
for (var i = 0; i < totalSteps; i++) {
circles.add(
_makeCirle(this.activeColor, this.inactiveColor, i, this.currentStep),
);
}
this.stepper = Container(
child: Row(
children: circles,
),
);
}
_makeNextArrow() {
this.nextArrow = Container(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: () {
_welcomeBloc.controller.nextPage(
duration: this.duration ?? Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Icon(
Icons.arrow_forward,
)),
),
);
}
_onLastPage() {
if (this.onFinal != null) {
this.onFinal();
}
}
_onFirstPage() {
if (this.onStart != null) {
this.onStart();
}
}
_detectLastPage() {
int currentPage = this.currentStep == null ? 1 : this.currentStep + 1;
if (currentPage == 1 && this.currentStep == null) {
this._onFirstPage();
} else if (currentPage == this.totalSteps) {
this.lastPage = true;
this._onLastPage();
} else {
this.lastPage = false;
}
}
}
If I got it right you are trying to simulate PageView navigation by some circle bellow it(Indicators).
To do so there are lots of good resources and also packages like:
This example or this package
But for your code I wrote it in 2 approaches:
First Approach
This one is your code and use provider.
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
void main() {
runApp(Home());
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => WelcomeBloc(),
child: Consumer<WelcomeBloc>(
builder: (BuildContext context, value, Widget child) {
PageController controller = value.controller;
print('object');
return MaterialApp(
home: Scaffold(
body: Stack(
children: <Widget>[
PageView(
controller: controller,
children: List.generate(
10, (i) => Center(child: Text('Page $i'))),
onPageChanged: (i) {
value.currentPage = i;
},
),
Footer(
activeColor: Colors.red,
duration: Duration(seconds: 1),
inactiveColor: Colors.yellow,
onFinal: () {},
onStart: () {},
totalSteps: 10,
)
],
),
),
);
},
),
);
}
}
class Footer extends StatefulWidget {
final int totalSteps;
final Color activeColor;
final Color inactiveColor;
final Duration duration;
final Function onFinal;
final Function onStart;
final double radius;
final double distance;
Footer({
this.activeColor,
this.inactiveColor,
this.totalSteps,
this.duration,
this.onFinal,
this.onStart,
this.radius = 10.0,
this.distance = 4.0,
});
#override
_FooterState createState() => _FooterState();
}
class _FooterState extends State<Footer> {
bool lastPage;
WelcomeBloc _welcomeBloc;
#override
Widget build(BuildContext context) {
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
// this._welcomeBloc.lastPage = true; // I'd like to set the state here
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_makeStepper(),
_makeNextArrow(),
],
),
);
}
_makeCircle(activeColor, inactiveColor, position, currentStep) {
currentStep = currentStep ?? 0;
Color color = (position == currentStep) ? activeColor : inactiveColor;
return Container(
height: widget.radius,
width: widget.radius,
margin: EdgeInsets.only(left: widget.distance, right: widget.distance),
decoration: BoxDecoration(
color: color,
border: Border.all(color: activeColor, width: 2.0),
borderRadius: BorderRadius.circular(50.0)),
);
}
_makeNextArrow() {
return Container(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: () async {
await _welcomeBloc.nextPage(widget.duration, Curves.easeInOut);
setState(() {});
},
child: Icon(
Icons.arrow_forward,
)),
),
);
}
_makeStepper() {
return Container(
child: Row(
children: List.generate(
widget.totalSteps,
(i) => _makeCircle(
this.widget.activeColor,
this.widget.inactiveColor,
i,
_welcomeBloc.currentPage,
),
),
),
);
}
_onLastPage() {
if (this.widget.onFinal != null) {
this.widget.onFinal();
}
}
_onFirstPage() {
if (this.widget.onStart != null) {
this.widget.onStart();
}
}
_detectLastPage() {
int currentPage =
_welcomeBloc.currentPage == null ? 1 : _welcomeBloc.currentPage + 1;
if (currentPage == 1 && _welcomeBloc.currentPage == null) {
this._onFirstPage();
} else if (currentPage == this.widget.totalSteps) {
this.lastPage = true;
this._onLastPage();
} else {
this.lastPage = false;
}
}
}
class WelcomeBloc extends ChangeNotifier {
final PageController _controller = PageController();
int _currentPage = 0;
bool _lastPage = false;
bool get lastPage => _lastPage;
set lastPage(bool value) {
_lastPage = value;
notifyListeners();
}
int get currentPage => _currentPage;
set currentPage(int value) {
_currentPage = value;
notifyListeners();
}
PageController get controller => _controller;
Future<void> nextPage(Duration duration, Curve curve) {
currentPage = controller.page.floor() + 1;
return controller.nextPage(duration: duration, curve: curve);
}
}
Second Approach
In the second one I removed provider stuff because it can be done without it by using PageController features.
import 'package:flutter/material.dart';
void main() {
runApp(Home());
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
PageController controller = PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Stack(
children: <Widget>[
PageView(
controller: controller,
children: List.generate(
10,
(i) => Center(child: Text('Page $i')),
),
onPageChanged: (page) {
setState(() {});
},
),
Footer(
currentPage: controller.hasClients ? controller.page.floor() : 0,
activeColor: Colors.red,
inactiveColor: Colors.yellow,
totalSteps: 10,
onTap: () async {
await controller.nextPage(
duration: Duration(seconds: 1) ?? Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
setState(() {});
},
)
],
),
),
);
}
}
class Footer extends StatelessWidget {
final int totalSteps;
final Color activeColor;
final Color inactiveColor;
final double radius;
final double distance;
final int currentPage;
final GestureTapCallback onTap;
Footer({
this.activeColor,
this.inactiveColor,
this.totalSteps,
this.radius = 10.0,
this.distance = 4.0,
this.currentPage,
this.onTap,
});
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
_makeStepper(),
_makeNextArrow(),
],
),
);
}
_makeCircle(activeColor, inactiveColor, position) {
Color color = (position == currentPage) ? activeColor : inactiveColor;
return Container(
height: radius,
width: radius,
margin: EdgeInsets.only(left: distance, right: distance),
decoration: BoxDecoration(
color: color,
border: Border.all(color: activeColor, width: 2.0),
borderRadius: BorderRadius.circular(50.0)),
);
}
_makeNextArrow() {
return Container(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: onTap,
child: Icon(
Icons.arrow_forward,
)),
),
);
}
_makeStepper() {
return Container(
child: Row(
children: List.generate(
totalSteps,
(i) => _makeCircle(
this.activeColor,
this.inactiveColor,
i,
),
),
),
);
}
}
So, the solution of my error is in didChangeDependencies hook.
I tried to change the state above at the very moment when the Widget was being built (that's how I got it).
So, I just needed to run it either before or after widget is built.
That's how it looks like in the code:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:ui_flutter/screens/welcome/welcome_bloc.dart';
import 'package:flutter/scheduler.dart';
class Footer extends StatefulWidget {
final int currentStep;
final int totalSteps;
final Color activeColor;
final Color inactiveColor;
final Duration duration;
final Function onFinal;
final Function onStart;
Footer({
this.activeColor,
this.inactiveColor,
this.currentStep,
this.totalSteps,
this.duration,
this.onFinal,
this.onStart,
}) {}
#override
_FooterState createState() => _FooterState();
}
class _FooterState extends State<Footer> {
final double radius = 10.0;
final double distance = 4.0;
Container stepper;
Container nextArrow;
bool lastPage;
WelcomeBloc _welcomeBloc;
#override
void didChangeDependencies() {
super.didChangeDependencies();
final WelcomeBloc _welcome = Provider.of<WelcomeBloc>(context);
_welcomeBloc = _welcome;
this._detectLastPage();
}
#override
Widget build(BuildContext context) {
this._makeStepper();
this._makeNextArrow();
return Container(
alignment: Alignment.bottomCenter,
padding: EdgeInsets.symmetric(vertical: 30.0, horizontal: 30.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
this.stepper,
this.nextArrow,
],
),
);
}
_makeCirle(activeColor, inactiveColor, position, currentStep) {
currentStep = currentStep == null ? 0 : currentStep - 1;
Color color = (position == currentStep) ? activeColor : inactiveColor;
return Container(
height: this.radius,
width: this.radius,
margin: EdgeInsets.only(left: this.distance, right: this.distance),
decoration: BoxDecoration(
color: color,
border: Border.all(color: activeColor, width: 2.0),
borderRadius: BorderRadius.circular(50.0)),
);
}
_makeStepper() {
List<Container> circles = List();
for (var i = 0; i < widget.totalSteps; i++) {
circles.add(
_makeCirle(this.widget.activeColor, this.widget.inactiveColor, i,
this.widget.currentStep),
);
}
this.stepper = Container(
child: Row(
children: circles,
),
);
}
_makeNextArrow() {
this.nextArrow = Container(
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: GestureDetector(
onTap: () {
_welcomeBloc.controller.nextPage(
duration: this.widget.duration ?? Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
},
child: Icon(
Icons.arrow_forward,
)),
),
);
}
_onLastPage() {
if (this.widget.onFinal != null) {
this.widget.onFinal();
}
}
_onFirstPage() {
if (this.widget.onStart != null) {
this.widget.onStart();
}
}
_detectLastPage() {
// Here I've got inaccurate data
int currentPage =
this.widget.currentStep == null ? 1 : this.widget.currentStep;
if (currentPage == 1 && this.widget.currentStep == null) {
this._onFirstPage();
} else if (currentPage == this.widget.totalSteps) {
print('lastPage detected');
setState(() {
this.lastPage = true;
});
_welcomeBloc.lastPage = true;
this._onLastPage();
} else {
setState(() {
this.lastPage = false;
});
_welcomeBloc.lastPage = false;
}
}
}
P.S.: but I face another problem there with the data accuracy. Inside that hook I could get the class property only one step behind accurate one.
details here: didChangeDependencies hook in Flutter Widget includes not accurate data of the class