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
Related
Hi i have a riveanimation that changes flower when the user has checkin, however the page of the animation would not change state once build as it is in a bottomnavigationbar. Is there anyway to reload the page to change the state of the animation. I only want to reload only one page and maintain the others.
This is the animation picture, it should change the leaf to green
here is my bottomnavigationbar page
class bottomnavbar extends StatefulWidget {
const bottomnavbar ({Key? key}) : super(key: key);
#override
_bottomnavbarState createState() => _bottomnavbarState();
}
class _bottomnavbarState extends State<bottomnavbar> {
double screenHeight = 0;
double screenWidth = 0;
String currentDate = DateFormat('yyyy-MM-dd').format(DateTime.now());
Color primary = const Color(0xffeef444c);
int currentIndex = 0;
List<IconData> navigationIcons = [
FontAwesomeIcons.personPraying,
FontAwesomeIcons.leaf,
FontAwesomeIcons.dice,
FontAwesomeIcons.person,
];
#override
Widget build(BuildContext context) {
screenHeight = MediaQuery.of(context).size.height;
screenWidth = MediaQuery.of(context).size.width;
print("Current save date is ${Provider.of<checkinlist>(context, listen: false).checkDate()}");
if( Provider.of<checkinlist>(context, listen: false).checkDate() == 'not set'){
Provider.of<checkinlist>(context, listen: false).saveDatenow();
}
else if (Provider.of<checkinlist>(context, listen: false).checkDate() != currentDate ){
Provider.of<checkinlist>(context, listen: false).overwriteSaveDate();
Provider.of<checkinlist>(context, listen: false).dailyreset();
}
else if (Provider.of<checkinlist>(context, listen: false).checkDate() == currentDate ){
}
return Scaffold(
backgroundColor: Colors.blueGrey[900],
body: IndexedStack(
index: currentIndex,
children: const [
mainmenu(),
FlowerGarden(),
Gamespage(),
ProfilePage(),
],
),
bottomNavigationBar: Container(
height: 70,
margin: const EdgeInsets.only(
left: 12,
right: 12,
bottom: 24,
),
decoration: const BoxDecoration(
color: Colors.lime,
borderRadius: BorderRadius.all(Radius.circular(40)),
boxShadow: [
BoxShadow(
color: Colors.black26,
blurRadius: 10,
offset: Offset(2, 2),
),
],
),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(40)),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
for(int i = 0; i < navigationIcons.length; i++)...<Expanded>{
Expanded(
child: GestureDetector(
onTap: () {
setState(() {
currentIndex = i;
});
},
child: Container(
height: screenHeight,
width: screenWidth,
color: Colors.deepPurple[900],
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
navigationIcons[i],
color: i == currentIndex ? primary : Colors.white60,
size: i == currentIndex ? 30 : 26,
),
i == currentIndex ? Container(
margin: const EdgeInsets.only(top: 6),
height: 3,
width: 22,
decoration: BoxDecoration(
borderRadius: const BorderRadius.all(Radius.circular(40)),
color: primary,
),
) : const SizedBox(),
],
),
),
),
),
),
}
],
),
),
),
);
}
}
And this is my riveanimation page
class FlowerGarden extends StatefulWidget{
const FlowerGarden({Key? key,
}) : super(key: key);
#override
_SimpleFlowerGardenState createState() => _SimpleFlowerGardenState();
}
final myCoordinates = Coordinates(3.139003, 101.686855);
// Replace with your own location lat, lng.
final params = CalculationMethod.karachi.getParameters();
final prayerTimes = PrayerTimes.today(myCoordinates, params);
class _SimpleFlowerGardenState extends State<FlowerGarden>{
SMITrigger? _SubuhEarly;
SMITrigger? _SubuhLate;
SMITrigger? _ZohorEarly;
SMITrigger? _ZohorLate;
SMITrigger? _AsarEarly;
SMITrigger? _AsarLate;
SMITrigger? _MaghribEarly;
SMITrigger? _MaghribLate;
SMITrigger? _IsyakEarly;
SMITrigger? _IsyakLate;
SMITrigger? _solatPerfect;
SMITrigger? _solatImperfect;
Artboard? _riveArtboard;
String? message;
#override
void initState() {
super.initState();
// Load the animation file from the bundle, note that you could also
// download this. The RiveFile just expects a list of bytes.
rootBundle.load('android/assets/lotus.riv').then(
(data) async {
// Load the RiveFile from the binary data.
final file = RiveFile.import(data);
// The artboard is the root of the animation and gets drawn in the
// Rive widget.
final artboard = file.mainArtboard;
var controller =
StateMachineController.fromArtboard(artboard, 'State Machine ',onStateChange: _onStateChange);
if (controller != null) {
artboard.addController(controller);
_SubuhEarly = controller.findInput<bool>('Subuh Early') as SMITrigger;
_SubuhLate = controller.findInput<bool>('Subuh Late') as SMITrigger;
_ZohorEarly = controller.findInput<bool>('Zohor Early') as SMITrigger;
_ZohorLate = controller.findInput<bool>('Zohor late') as SMITrigger;
_AsarEarly = controller.findInput<bool>('Asar Early') as SMITrigger;
_AsarLate = controller.findInput<bool>('Asar Late') as SMITrigger;
_MaghribEarly = controller.findInput<bool>('Maghrib early') as SMITrigger;
_MaghribLate = controller.findInput<bool>('Maghrib late') as SMITrigger;
_IsyakEarly = controller.findInput<bool>('Ishak early') as SMITrigger;
_IsyakLate = controller.findInput<bool>('Ishak late') as SMITrigger;
_solatPerfect = controller.findInput<bool>('Solat Perfect') as SMITrigger;
_solatImperfect = controller.findInput<bool>('Solat Imperfect') as SMITrigger;
}
setState(() => _riveArtboard = artboard);
},
);
}
void _onStateChange(
String stateMachineName,
String stateName,
) =>
setState(
() => print('State Changed in $stateMachineName to $stateName') ,
);
void flowercheck(){
if (Provider.of<checkinlist>(context, listen: false).getSubuh() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getZohor() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getAsar() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getMaghrib() == 2){
_solatImperfect?.fire();
}
else if (Provider.of<checkinlist>(context, listen: false).getIsyak() == 2){
_solatImperfect?.fire();
}
else {
_solatPerfect?.fire();
}
}
void leafcheck(){
int subuh = Provider.of<checkinlist>(context, listen: false).getSubuh();
int zohor = Provider.of<checkinlist>(context, listen: false).getZohor();
int asar = Provider.of<checkinlist>(context, listen: false).getAsar();
int maghrib = Provider.of<checkinlist>(context, listen: false).getMaghrib();
int isyak = Provider.of<checkinlist>(context, listen: false).getIsyak();
double health = Provider.of<solatPoints>(context, listen: false).getHealth();
switch(subuh){
case 1:
_SubuhEarly?.fire();
break;
case 2:
_SubuhLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(zohor){
case 1:
_ZohorEarly?.fire();
break;
case 2:
_ZohorLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(asar){
case 1:
_AsarEarly?.fire();
break;
case 2:
_AsarLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(maghrib){
case 1:
_MaghribEarly?.fire();
break;
case 2:
_MaghribLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
}
switch(isyak){
case 1:
_IsyakEarly?.fire();
break;
case 2:
_IsyakLate?.fire();
if (health > 5) {
Provider.of<solatPoints>(context, listen: false).decreasehealth();
}
break;
default:
break;
}
}
#override
Widget build(BuildContext context) {
setState(() {
leafcheck();
});
return Scaffold(
backgroundColor: Colors.blueGrey[900],
appBar: AppBar(
backgroundColor: Colors.deepPurple[900],
title: const Text('Bunga Solat'),
),
body: Center(
child: GestureDetector(
child: _riveArtboard == null
? const SizedBox()
: Stack(
children: [
Positioned.fill(
child: Rive(
artboard: _riveArtboard!,
),
),
]
),
),
)
);
}
}
class ClassProfile extends State<ClassProfile> with AutomaticKeepAliveClientMixin {
#override
// TODO: implement wantKeepAlive
bool get wantKeepAlive => false;
#override
Widget build(BuildContext context) {
if(wantKeepAlive) {
super.build(context);
}
.
.
.
.
first set - with AutomaticKeepAliveClientMixin
then, override wantkeepalive variable
at last, defined super.build with if condition in your main widget builder function
In future if you want to persist state of your class then set wantKeepAlive => true
I've made my own social media app with Flutter and it has been awesome, however, I'm struggling to stop the chat messages from rebuilding with my paginated firebase list. I've added print statements and refactored everything to stateless widgets or stateful widgets with AutomaticKeepAliveClientMixins, but to no avail.
The problem is worst with link previews and video thumbnails as these disappear and reappear constantly while scrolling, leading to an awful experience.
Paginated List Code Below:
#override
Widget build(BuildContext context) {
return BlocBuilder<PaginationCubit, PaginationState>(
bloc: _cubit,
builder: (context, state) {
if (state is PaginationInitial) {
return widget.initialLoader;
} else if (state is PaginationError) {
return (widget.onError != null)
? widget.onError!(state.error)
: Container();
} else {
final loadedState = state as PaginationLoaded;
if (widget.onLoaded != null) {
widget.onLoaded!(loadedState);
}
if (loadedState.hasReachedEnd && widget.onReachedEnd != null) {
widget.onReachedEnd!(loadedState);
}
if (loadedState.messages.isEmpty) {
return widget.emptyDisplay;
}
return _buildListView(loadedState);
}
},
);
}
Widget _buildListView(PaginationLoaded loadedState) {
return Padding(
padding: widget.padding,
child: ListView.builder(
itemCount: max(
0,
(loadedState.hasReachedEnd
? loadedState.messages.length
: loadedState.messages.length + 1) *
2 -
1),
itemBuilder: (context, index) {
final itemIndex = index ~/ 2;
if (index.isEven) {
if (itemIndex >= loadedState.messages.length) {
_cubit.fetchPaginatedList();
return widget.bottomLoader;
}
return widget.itemBuilder(
itemIndex,
context,
loadedState.messages[itemIndex], // current message
loadedState.messages[itemIndex == loadedState.messages.length - 1
? loadedState.messages.length - 1
: (itemIndex + 1)], // previous message
loadedState.messages[
itemIndex == 0 ? 0 : (itemIndex - 1)], // next message
loadedState.messages.length,
);
}
return widget.separator!(
itemIndex,
context,
loadedState.messages[itemIndex],
);
},
),
);
}
The main message widget
class MessageItem extends StatefulWidget {
final Message message;
final Message previousMessage;
final Message previousUserMessage;
final Message nextMessage;
final Chat chat;
final bool showReactionPickerIndicator;
final bool showReaction;
final Function? onMoveScroll;
const MessageItem({
Key? key,
required this.message,
required this.previousMessage,
required this.previousUserMessage,
required this.nextMessage,
required this.chat,
this.showReaction = true,
this.showReactionPickerIndicator = false,
this.onMoveScroll,
}) : super(key: key);
MessageItem copyWith(
{Key? key,
Message? message,
bool? showReactionPickerIndicator,
bool? showReaction}) {
return MessageItem(
key: key ?? this.key,
message: message ?? this.message,
previousMessage: previousMessage,
previousUserMessage: previousUserMessage,
nextMessage: nextMessage,
showReaction: showReaction ?? this.showReaction,
showReactionPickerIndicator:
showReactionPickerIndicator ?? this.showReactionPickerIndicator,
chat: chat);
}
#override
State<MessageItem> createState() => _MessageItemState();
}
class _MessageItemState extends State<MessageItem>
with AutomaticKeepAliveClientMixin {
final messageController = MessageController.to;
#override
bool get wantKeepAlive => true;
late final bool currentUserIsAuthor;
late final bool nextMessageIsOwned;
late final bool lastMessageIsOwned;
late final bool isLastMessage;
late final bool showReactionWidget;
late final bool showAuthorName;
late final bool showNip;
late final bool showAvatar;
late final bool alignWithAvatar;
#override
void initState() {
debugPrint('[MessageItem] - initState');
if (widget.onMoveScroll != null) {
widget.onMoveScroll!();
}
final currentUser = getCurrentUser()!;
// Message setup booleans
currentUserIsAuthor = widget.message.authorId == currentUser.uid;
nextMessageIsOwned = widget.message.authorId == widget.nextMessage.authorId;
lastMessageIsOwned =
widget.message.authorId == widget.previousMessage.authorId;
isLastMessage = widget.nextMessage.id == widget.message.id;
// Message ui booleans
showReactionWidget =
widget.showReaction && widget.message.reactions.isNotEmpty;
showAuthorName = widget.chat.chatType == ChatType.GROUP &&
!currentUserIsAuthor &&
!lastMessageIsOwned;
showNip = widget.message == widget.nextMessage ||
widget.nextMessage.authorId != widget.message.authorId;
showAvatar = showNip && widget.chat.chatType == ChatType.GROUP;
alignWithAvatar = widget.chat.chatType == ChatType.GROUP && !showNip;
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context);
return SwipeTo(
iconOnRightSwipe: CupertinoIcons.reply_thick_solid,
onRightSwipe: () {
messageController.replyMessage.value = widget.message;
},
child: Material(
type: MaterialType.transparency,
child: Portal(
child: FocusedMenuWrapper(
items: messageMenuItems(
context,
widget.message,
widget.previousUserMessage,
isLastMessage,
currentUserIsAuthor,
widget.chat,
widget.copyWith(
key: const Key('MessageWidget'),
showReactionPickerIndicator: true,
showReaction: false),
),
child: Padding(
padding: _calculateMessagePadding(
lastMessageIsOwned, nextMessageIsOwned),
child: FractionallySizedBox(
alignment: currentUserIsAuthor
? Alignment.centerRight
: Alignment.centerLeft,
widthFactor: 0.8,
child: Column(
crossAxisAlignment: currentUserIsAuthor
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
children: [
if (showAvatar && !currentUserIsAuthor)
MessageUserAvatar(
userName: widget.message.authorName,
userProfile: widget.message.authorProfile),
Flexible(
child: Padding(
padding: EdgeInsets.only(
left: !currentUserIsAuthor && alignWithAvatar
? 26.w
: 0,
right: currentUserIsAuthor && alignWithAvatar
? 26.w
: 0),
child: ReactionPortalWrapper(
showReactionWidget: showReactionWidget,
showReaction: widget.showReaction,
message: widget.message,
chat: widget.chat,
isCurrentUserAuthor: currentUserIsAuthor,
messageWidget: widget,
showAuthorName: showAuthorName,
showNip: showNip,
showReactionPickerWidget:
widget.showReactionPickerIndicator,
),
)),
if (showAvatar && currentUserIsAuthor)
MessageUserAvatar(
userName: widget.message.authorName,
userProfile: getCurrentUser()!.profileImg),
],
)
],
)),
),
),
),
),
);
}
}
The Message Bubble (where I suspect something is going wrong)
class MessageBubble extends StatelessWidget {
final bool showNip;
final bool isCurrentUserAuthor;
final Message message;
final Chat chat;
const MessageBubble(
{Key? key,
required this.showNip,
required this.isCurrentUserAuthor,
required this.message,
required this.chat})
: super(key: key);
#override
Widget build(BuildContext context) {
return Bubble(
elevation: 0,
nip: showNip
? isCurrentUserAuthor
? BubbleNip.rightBottom
: BubbleNip.leftBottom
: BubbleNip.no,
radius: Radius.circular(24.r),
nipHeight: 12.h,
nipOffset: 5.h,
nipRadius: 2,
child: message.replyMessage != null
? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildReplyMessageBody(context, isCurrentUserAuthor, message),
MessageBubbleBody(message: message, chat: chat)
],
)
: MessageBubbleBody(message: message, chat: chat));
}
Widget _buildReplyMessageBody(
BuildContext context, bool currentUserIsAuthor, Message? message) {
return Container(
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(6.w),
decoration: BoxDecoration(
color: Theme.of(context).brightness == Brightness.light
? Colors.white54
: Colors.black54,
borderRadius: BorderRadius.circular(8.r),
),
child: ReplyMessage(chat: chat, message: message!),
);
}
}
And the bubble body
class MessageBodyWithTimestamp extends StatelessWidget {
final Widget body;
final Widget timestamp;
const MessageBodyWithTimestamp(
{Key? key, required this.body, required this.timestamp})
: super(key: key);
#override
Widget build(BuildContext context) {
return IntrinsicWidth(
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
body,
timestamp,
],
),
);
}
}
class MessageBubbleBody extends StatelessWidget {
final Message message;
final Chat chat;
const MessageBubbleBody({Key? key, required this.message, required this.chat})
: super(key: key);
#override
Widget build(BuildContext context) {
if (message.sendStatus == SendStatus.ERROR) {
return const SizedBox.shrink();
}
if (message.messageType == MessageType.MEDIA) {
if (message.duration != null) {
return MediaTimerMessage(message: message, chatId: chat.id);
} else {
return Stack(
fit: StackFit.loose,
children: [
MediaMessage(message: message, chatId: chat.id),
Positioned(
bottom: 15.h,
right: 15.w,
child: _buildTimestampReadContent(context))
],
);
}
} else if (message.messageType == MessageType.STICKER) {
return Stack(
fit: StackFit.loose,
children: [
StickerMessage(message: message, chatId: chat.id),
Positioned(
bottom: 15.h,
right: 15.w,
child: _buildTimestampReadContent(context)),
],
);
} else if (message.messageType == MessageType.AUDIO) {
if (message.mediaStatus == MediaStatus.UPLOADING) {
return MessageBodyWithTimestamp(
body: const AudioUploadingMessage(),
timestamp: _buildTimestampReadContent(context));
} else {
return MessageBodyWithTimestamp(
body: AudioMessage(message: message),
timestamp: _buildTimestampReadContent(context));
}
} else if (message.messageType == MessageType.FILE) {
return MessageBodyWithTimestamp(
body: FileMessage(message: message, chatId: chat.id),
timestamp: _buildTimestampReadContent(context));
} else {
return MessageBodyWithTimestamp(
body: TextMessage(message: message, chatId: chat.id),
timestamp: _buildTimestampReadContent(context));
}
}
}
And finally (sorry I know this is getting very long)... the text message with link preview
class TextMessage extends StatefulWidget {
final Message message;
final String chatId;
const TextMessage({Key? key, required this.message, required this.chatId})
: super(key: key);
#override
State<TextMessage> createState() => _TextMessageState();
}
class _TextMessageState extends State<TextMessage>
with AutomaticKeepAliveClientMixin {
late final List<LinkifyElement> elements;
late final List<LinkifyElement> links;
#override
bool get wantKeepAlive => true;
#override
void initState() {
print('Building text message');
elements = linkify(
widget.message.body.body,
options: const LinkifyOptions(humanize: false),
);
links = elements
.where((element) =>
element is LinkableElement &&
!element.text.contains('media.giphy.com'))
.toList();
super.initState();
}
#override
Widget build(BuildContext context) {
super.build(context);
bool isUserSender = widget.message.authorId == getCurrentUser()!.uid;
if (links.isNotEmpty) {
return TextMessageWithLink(
elements: elements, isUserSender: isUserSender);
} else {
return Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 25.w),
child: Padding(
padding: const EdgeInsets.all(3),
child: Align(
alignment: Alignment.topLeft,
child: RichText(
textScaleFactor: MediaQuery.of(context).textScaleFactor,
text: buildTextSpan(
elements,
style: AppTextStyles.messageTextStyle(context, isUserSender),
onOpen: (link) async {
_launch(link.url);
},
linkStyle: AppTextStyles.linkTextStyle(context, isUserSender),
),
),
),
),
),
);
}
}
}
With the preview...
class TextMessageWithLink extends StatelessWidget {
final List<LinkifyElement> elements;
final bool isUserSender;
const TextMessageWithLink(
{Key? key, required this.elements, required this.isUserSender})
: super(key: key);
#override
Widget build(BuildContext context) {
return Flexible(
child: ConstrainedBox(
constraints: BoxConstraints(minWidth: 25.w),
child: Padding(
padding: const EdgeInsets.all(3),
child: Align(
alignment: Alignment.topLeft,
child: Column(
children: List.generate(elements.length, (index) {
if (elements[index] is LinkableElement &&
elements[index].text.contains('media.giphy.com') == false) {
return Column(
children: [
_buildUrlPreview(context, isUserSender, index),
GestureDetector(
onTap: () async {
_launch(elements[index].text);
},
child: Text(elements[index].text,
style: AppTextStyles.linkTextStyle(
context, isUserSender)),
)
],
);
} else if (elements[index].text.contains('media.giphy.com')) {
return GestureDetector(
onTap: () async {
_launch(elements[index].text);
},
child: Text(elements[index].text,
style:
AppTextStyles.linkTextStyle(context, isUserSender)),
);
} else {
return Text(elements[index].text,
style: AppTextStyles.messageTextStyle(
context, isUserSender));
}
}),
),
),
),
),
);
}
Widget _buildUrlPreview(BuildContext context, bool isUserSender, int index) {
return SimpleUrlPreview(
url: elements[index].text,
titleStyle: AppTextStyles.messageTextStyle(context, isUserSender)
.copyWith(fontWeight: FontWeight.w600),
descriptionStyle: AppTextStyles.messageTextStyle(context, isUserSender),
bgColor: CupertinoDynamicColor.resolve(
CupertinoColors.tertiarySystemGroupedBackground, context),
isClosable: false,
imageLoaderColor:
CupertinoDynamicColor.resolve(CupertinoColors.label, context),
previewHeight: 130,
);
}
}
Thanks in advance if you can figure it out ;)
I'm passing data from a stateful widget, and I access it like in this example (widget.variable)
https://stackoverflow.com/a/50818870/806009
However, occasionally it throws an error (about 1 in 20) for a line in this comparison
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The method '>' was called on null.
Receiver: null
Tried calling: >(10)
if (widget.x > 10){...}
It seems that widget.x is not known at this time. Should I use initState to ensure that value is "ready"?
Such as
#override
void initState() {
super.initState();
this.x = widget.x
}
Full Code
class PlayerSelection extends StatefulWidget {
final int teamId;
final int leagueId;
final League.League league;
final String className;
List<PlayerObj> playersListOfClass;
final int index;
final int remaining;
PlayerSelection({Key key, #required this.teamId, this.leagueId, this.league, this.className, this.playersListOfClass, this.index, this.remaining}) : super(key: key);
#override
_PlayerSelection createState() => _PlayerSelection();
}
class _PlayerSelection extends State<PlayerSelection> {
var playerWidgets = <Widget>[];
List<PlayerObj> selectedPlayers = [];
List<PlayerObj> playerList = [];
PlayerObj draftedPlayer;
List<PlayerClass> playerClassList = [];
bool _isFavorited = false;
Modal modal = new Modal();
int lineupWeek = 0;
int lineupSize = 0;
String intervalLabel = '';
bool _is_full = false;
bool _is_locked = false;
int remaining = 0;
var currentColor = Colors.black;
var rejectedPlayerId;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return new Scaffold(
resizeToAvoidBottomPadding: false,
appBar: globals.MyAppBar(
leading: IconButton(icon: Icon(Icons.close),
onPressed: (){
Navigator.pop(context, {'player': null,'index': this.widget.index});
}), // go to league route,),
title: Text(widget.className)
),
body: Container(
child: Column(children: [Expanded(child: ListView(children: _getLineup(this.widget.playersListOfClass))),]),
),
);
}
List<Widget>_getLineup(playerList) {
List<Widget> widgets = [];
var index = 0;
playerList.forEach((player) =>
widgets.add(
Padding(
padding: const EdgeInsets.symmetric(horizontal:16.0),
child: Container(
decoration: BoxDecoration(
color: Colors.white,
border: Border(
bottom: BorderSide(width: 1.0, color: Colors.grey.withOpacity(0.3)),
),
),
child: new ListTile(
leading: GestureDetector(
onTap: (){
if(!_is_full) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
Players.PlayerDetail(playerId: player.playerId,
leagueId: widget.leagueId,
playerName: player.playerName,
playerBio: player.playerBio)),
);
}
},
child: ClipOval(
child: _playerPicWidget(player)
),
),
title: GestureDetector(
onTap: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) =>
Players.PlayerDetail(playerId: player.playerId,
leagueId: widget.leagueId,
playerName: player.playerName,
playerBio: player.playerBio)),
);
},
child: _playerNameWidget(player)
),
trailing: _trailingWidget(player),
onTap: () => player.playerPrice <= widget.remaining ? Navigator.pop(context, {'player': player,'index': this.widget.index}) : null,
// return player back
),
),
)
)
);
return widgets;
}
Widget _playerNameWidget(player){
if(this._is_full && !this.selectedPlayers.contains(player)){ //show disabled selection
return Opacity(opacity:.25, child:Text("${player.playerName}"));
}
else {
if(this.widget.league.hasBudget){ // can afford player, not full
if(player.playerPrice <= widget.remaining || this.selectedPlayers.contains(player)){
return Text("${player.playerName}");
}
else { // can't afford player, not full
return Opacity(opacity:.25, child:Text("${player.playerName}"));
}
}
else { // slot still open
return Text("${player.playerName}");
}
}
}
Widget _playerPicWidget(player){
if(player == this.draftedPlayer){
return Opacity(opacity: .25, child: Image.network('${player.playerImageUrl}',
fit: BoxFit.scaleDown,
height: 45,
width: 45,
));
}
else {
if(player.playerPrice <= widget.remaining || this.selectedPlayers.contains(player)){
return Image.network('${player.playerImageUrl}',
fit: BoxFit.scaleDown,
height: 45,
width: 45,
);
}
}
}
Widget _trailingWidget(player){
List<Widget> tWidgets;
double playerOpacity = 1;
if(player.playerPrice > widget.remaining){
playerOpacity = .25;
}
tWidgets = [
Padding(padding: const EdgeInsets.symmetric(horizontal:10.0),
child: Opacity(opacity:playerOpacity, child:Text("\$${globals.commaFormat.format(player.playerPrice)}")),
), Opacity(opacity: playerOpacity, child: Icon(Icons.add))];
return Row(mainAxisSize: MainAxisSize.min, children: tWidgets);
}
}
The issue was a data issue unrelated to the passing of data.
I am trying to recreate RobinHood slide number animation, I am treating every digit as an Item in n animated list, When I am adding an digit to the start of the list the last Item in the list receives a jump and I cant quite figure out how to fix it.
this is the code for every digit
class NumberColView extends StatefulWidget {
final int animateTo;
final bool comma;
final TextStyle textStyle;
final Duration duration;
final Curve curve;
NumberColView(
{#required this.animateTo,
#required this.textStyle,
#required this.duration,
this.comma = false,
#required this.curve})
: assert(animateTo != null && animateTo >= 0 && animateTo < 10);
#override
_NumberColState createState() => _NumberColState();
}
class _NumberColState extends State<NumberColView>
with SingleTickerProviderStateMixin {
ScrollController _scrollController;
double _elementSize = 0.0;
#override
void initState() {
super.initState();
print(_elementSize);
_scrollController = new ScrollController();
WidgetsBinding.instance.addPostFrameCallback((_) {
_elementSize = _scrollController.position.maxScrollExtent / 10;
setState(() {});
});
}
#override
void didUpdateWidget(NumberColView oldWidget) {
if (oldWidget.animateTo != widget.animateTo) {
_scrollController.animateTo(_elementSize * widget.animateTo,
duration: widget.duration, curve: widget.curve);
}
super.didUpdateWidget(oldWidget);
}
#override
Widget build(BuildContext context) {
// print(widget.animateTo);
return Row(
mainAxisSize: MainAxisSize.min,
children: [
IgnorePointer(
child: ConstrainedBox(
constraints: BoxConstraints(maxHeight: _elementSize),
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: List.generate(10, (position) {
return Text(position.toString(), style: widget.textStyle);
}),
),
),
),
),
widget.comma
? Container(
child: Text(', ',
style:
TextStyle(fontSize: 16, fontWeight: FontWeight.bold)))
: Container(),
],
);
}
}
This the code to build the whole number
class _NumberSlideAnimationState extends State<NumberSlideAnimation> {
#override
void didUpdateWidget(oldWidget) {
if (oldWidget.number.length != widget.number.length) {
int newLen = widget.number.length - oldWidget.number.length;
if(newLen > 0) {
widget.listKey.currentState.insertItem(0,
duration: const Duration(milliseconds: 200));
}
// setState(() {
// animateTo = widget.animateTo;
// });
}
super.didUpdateWidget(oldWidget);
}
Widget digitSlide(BuildContext context, int position, animation) {
int item = int.parse(widget.number[position]);
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(0, 1),
end: Offset(0, 0),
).animate(animation),
child:
// TestCol(animateTo: item, style: widget.textStyle)
NumberColView(
textStyle: widget.textStyle,
duration: widget.duration,
curve: widget.curve,
comma: (widget.number.length - 1) != position &&
(widget.number.length - position) % 3 == 1 ??
true,
animateTo: item,
),
);
}
#override
Widget build(BuildContext context) {
return Container(
height: 40,
child: AnimatedList(
shrinkWrap: true,
scrollDirection: Axis.horizontal,
key: widget.listKey,
initialItemCount: widget.number.length,
itemBuilder: (context, position, animation) {
return digitSlide(context, position, animation);
},
),
);
}
}
I don't know the right solution for your problem, however I know another way and I am just sharing it.
Here the full code If you want to use this method:
update: now 0 will come form bottom as what it should be and the uncharged values will be in there palaces.
class Count extends StatefulWidget {
const Count({Key key}) : super(key: key);
#override
_CountState createState() => _CountState();
}
class _CountState extends State<Count> {
int count = 10;
int count2 = 10;
int count3 = 10;
Alignment alignment = Alignment.topCenter;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRRect(
child: Align(
alignment: Alignment.center,
heightFactor: 0.2,
child: SizedBox(
height: 100,
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
...List.generate(count.toString().length, (index) {
final num2IndexInRange =
count2.toString().length - 1 >= index;
final num3IndexInRange =
count3.toString().length - 1 >= index;
final num1 = int.parse(count.toString()[index]),
num2 = num2IndexInRange
? int.parse(count2.toString()[index])
: 9,
num3 = num3IndexInRange
? int.parse(count3.toString()[index])
: 1;
return AnimatedAlign(
key: Key("${num2} ${index}"),
duration: Duration(milliseconds: 500),
alignment: (num1 != num2 || num1 != num3)
? alignment
: Alignment.center,
child: Text(
num3.toString(),
style: TextStyle(
fontSize: 20,
),
),
);
}),
],
),
),
),
),
SizedBox(height: 200),
RaisedButton(
onPressed: () async {
setState(() {
alignment = Alignment.topCenter;
count += 10;
});
await Future.delayed(Duration(milliseconds: 250));
setState(() {
alignment = Alignment.bottomCenter;
count2 = count;
});
await Future.delayed(Duration(milliseconds: 250));
setState(() {
count3 = count;
});
},
),
],
),
);
}
}
if you don't like how the spacing between the numbers changed you can warp the text widget with a sized box and give it a fixed width.
I want to create condition that if first api didn't finish load when click on bottom navigation tab it will not navigate to other tab. So I want to pass boolean that will change to true when api is loaded, the problem is I don't know how to pass this dynamic value to my custom Bottom Navigation Bar. Is there an other way instead of using global variable?
This is my code.
landingPage.dart
// This global variable is change to true when receive callback from api
bool isComplete = false;
class _NavigationItem {
_NavigationItem(this.iconFile, this.caption, this.page);
final String iconFile;
final String caption;
final Widget page;
}
class LandingPage extends StatefulWidget {
final String username;
LandingPage({Key key, this.username}) : super(key: key);
#override
_LandingPageState createState() => _LandingPageState();
}
class _LandingPageState extends State<LandingPage> {
int _selectedIndex = 0;
List<_NavigationItem> _navItems = [];
double _bottomNavBarHeight = 46;
double _iconSize = 26;
double _circleSize = 52;
int _animationDuration = 300;
double _circleStrokeWidth = 0;
BottomNavigationController _navigationController;
#override
void didChangeDependencies() {
// Context of a state is available to us from the moment the State loads its dependencies
// Since we need context object so it need to be accessed inside this overridden method.
_navItems = [
_NavigationItem(
"assets/icons/home.svg",
S.of(context).landing_nav_home,
HomePage(
username: widget.username,
callback: (value) { isComplete = value; }, // receiving callback data
)),
_NavigationItem("assets/icons/friend.svg",
S.of(context).landing_nav_friend, FriendPage()),
_NavigationItem(
"assets/icons/chat.svg", S.of(context).landing_nav_chat, ChatPage()),
_NavigationItem("assets/icons/widget.svg",
S.of(context).landing_nav_widget, WidgetPage()),
_NavigationItem("assets/icons/more.svg",
S.of(context).landing_nav_setting, SettingPage()),
];
super.didChangeDependencies();
}
#override
void initState() {
super.initState();
_navigationController = new BottomNavigationController(_selectedIndex);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Container(
padding: EdgeInsets.only(bottom: 90),
child: _navItems.elementAt(_selectedIndex).page,
),
_createBottomNavigationBar()
],
)
);
}
void _onNavigationBarItemTapped(int index) {
setState(() {
_selectedIndex = index;
});
}
Widget _createBottomNavigationBar() {
return Align(alignment: Alignment.bottomCenter, child: _bottomNav());
}
Widget _bottomNav() {
List<TabItem> tabItems = List.of([
new TabItem(Icons.home_outlined, "", CommonColor.accent,
asset: "assets/icons/home.svg"),
new TabItem(Icons.person, "", CommonColor.accent,
asset: "assets/icons/friends.svg"),
new TabItem(Icons.chat_bubble_outline, "", CommonColor.accent),
new TabItem(Icons.widgets_outlined, "", CommonColor.accent),
new TabItem(Icons.more_horiz, "", CommonColor.accent),
]);
return BottomNavigation(
tabItems,
controller: _navigationController,
barHeight: _bottomNavBarHeight,
iconsSize: _iconSize,
circleSize: _circleSize,
selectedIconColor: Colors.white,
normalIconColor: CommonColor.accent,
circleStrokeWidth: _circleStrokeWidth,
barBackgroundColor: Colors.white,
animationDuration: Duration(milliseconds: _animationDuration),
selectedCallback: (int selectedPos) {
print("selected: $_selectedIndex");
_onNavigationBarItemTapped(selectedPos);
},
);
}
#override
void dispose() {
super.dispose();
_navigationController.dispose();
}
}
BottomNavigation.dart
typedef BottomNavSelectedCallback = Function(int selectedPos);
class BottomNavigation extends StatefulWidget {
final List<TabItem> tabItems;
final int selectedPos;
final double barHeight;
final double padding;
final Color barBackgroundColor;
final double circleSize;
final double circleStrokeWidth;
final double iconsSize;
final Color selectedIconColor;
final Color normalIconColor;
final Duration animationDuration;
final BottomNavSelectedCallback selectedCallback;
final BottomNavigationController controller;
BottomNavigation(this.tabItems,
{this.selectedPos = 0,
this.barHeight = 60,
this.barBackgroundColor = Colors.white,
this.circleSize = 58,
this.circleStrokeWidth = 4,
this.iconsSize = 32,
this.padding = 16,
this.selectedIconColor = Colors.white,
this.normalIconColor = Colors.deepPurpleAccent,
this.animationDuration = const Duration(milliseconds: 300),
this.selectedCallback,
this.controller})
: assert(tabItems != null && tabItems.length >= 2 && tabItems.length <= 5,
"tabItems is required");
#override
State<StatefulWidget> createState() => _BottomNavigationState();
}
class _BottomNavigationState extends State<BottomNavigation>
with TickerProviderStateMixin {
Curve _animationsCurve = Cubic(0.27, 1.21, .77, 1.09);
AnimationController itemsController;
Animation<double> selectedPosAnimation;
Animation<double> itemsAnimation;
List<double> _itemsSelectedState;
int selectedPos;
int previousSelectedPos;
BottomNavigationController _controller;
#override
void initState() {
super.initState();
if (widget.controller != null) {
_controller = widget.controller;
previousSelectedPos = selectedPos = _controller.value;
} else {
previousSelectedPos = selectedPos = widget.selectedPos;
_controller = BottomNavigationController(selectedPos);
}
_controller.addListener(_newSelectedPosNotify);
_itemsSelectedState = List.generate(widget.tabItems.length, (index) {
return selectedPos == index ? 1.0 : 0.0;
});
itemsController = new AnimationController(
vsync: this, duration: widget.animationDuration);
itemsController.addListener(() {
setState(() {
_itemsSelectedState.asMap().forEach((i, value) {
if (i == previousSelectedPos) {
_itemsSelectedState[previousSelectedPos] =
1.0 - itemsAnimation.value;
} else if (i == selectedPos) {
_itemsSelectedState[selectedPos] = itemsAnimation.value;
} else {
_itemsSelectedState[i] = 0.0;
}
});
});
});
selectedPosAnimation = makeSelectedPosAnimation(
selectedPos.toDouble(), selectedPos.toDouble());
itemsAnimation = Tween(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: itemsController, curve: _animationsCurve));
}
Animation<double> makeSelectedPosAnimation(double begin, double end) {
return Tween(begin: begin, end: end).animate(
CurvedAnimation(parent: itemsController, curve: _animationsCurve));
}
void onSelectedPosAnimate() {
setState(() {});
}
void _newSelectedPosNotify() {
_setSelectedPos(widget.controller.value);
}
#override
Widget build(BuildContext context) {
double fullWidth = MediaQuery.of(context).size.width;
double fullHeight =
widget.barHeight + (widget.circleSize / 2) + widget.circleStrokeWidth;
double sectionsWidth = (fullWidth / 1.2) / widget.tabItems.length;
//Create the boxes Rect
List<Rect> boxes = List();
widget.tabItems.asMap().forEach((i, tabItem) {
double left = (i + 0.5) * sectionsWidth;
double top = fullHeight - widget.barHeight;
double right = left + sectionsWidth;
double bottom = fullHeight;
boxes.add(Rect.fromLTRB(left, top, right, bottom));
});
List<Widget> children = List();
// This is the full view transparent background (have free space for circle)
children.add(Padding(
padding: EdgeInsets.only(bottom: widget.padding),
child: Container(
width: fullWidth,
height: fullHeight,
)));
// This is the bar background (bottom section of our view)
children.add(Positioned.fill(
child: Padding(
padding: EdgeInsets.only(
left: widget.padding,
right: widget.padding,
bottom: widget.padding),
child: Container(
width: MediaQuery.of(context).size.width,
height: widget.barHeight,
decoration: BoxDecoration(
color: widget.barBackgroundColor,
borderRadius: BorderRadius.all(Radius.circular(20)),
boxShadow: [
new BoxShadow(color: Colors.black12, blurRadius: 8.0)
]),
),
),
top: fullHeight - widget.barHeight,
));
// This is the circle handle on selected
children.add(new Positioned(
child: Container(
width: widget.circleSize,
height: widget.circleSize,
child: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(14)),
color: widget.barBackgroundColor),
),
Container(
margin: EdgeInsets.all(widget.circleStrokeWidth),
decoration: BoxDecoration(
shape: BoxShape.rectangle,
borderRadius: BorderRadius.all(Radius.circular(14)),
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
CommonColor.primary,
widget.tabItems[selectedPos].circleColor,
CommonColor.slipBg,
])),
),
],
),
),
left: boxes[selectedPos].center.dx - (widget.circleSize / 2),
top: 0,
));
//Here are the Icons and texts of items
boxes.asMap().forEach((int pos, Rect r) {
// Icon
Color iconColor = pos == selectedPos
? widget.selectedIconColor
: widget.normalIconColor;
double scaleFactor = pos == selectedPos ? 1.2 : 1.0;
children.add(
Positioned(
child: Transform.scale(
scale: scaleFactor,
child: widget.tabItems[pos].asset != null
? SvgPicture.asset(widget.tabItems[pos].asset,
color: iconColor,
width: widget.iconsSize,
height: widget.iconsSize,
fit: BoxFit.cover)
: Icon(
widget.tabItems[pos].icon,
size: widget.iconsSize,
color: iconColor,
),
),
left: r.center.dx - (widget.iconsSize / 2),
top: r.center.dy -
(widget.iconsSize / 2) -
(_itemsSelectedState[pos] *
((widget.barHeight / 2) + widget.circleStrokeWidth)),
),
);
if (pos != selectedPos) {
children.add(Positioned.fromRect(
child: GestureDetector(
onTap: () {
_controller.value = pos;
},
),
rect: r,
));
}
});
return Stack(
children: children,
);
}
void _setSelectedPos(int pos) {
previousSelectedPos = selectedPos;
selectedPos = pos;
itemsController.forward(from: 0.0);
selectedPosAnimation = makeSelectedPosAnimation(
previousSelectedPos.toDouble(), selectedPos.toDouble());
selectedPosAnimation.addListener(onSelectedPosAnimate);
if (widget.selectedCallback != null) {
widget.selectedCallback(selectedPos);
}
}
#override
void dispose() {
super.dispose();
itemsController.dispose();
_controller.removeListener(_newSelectedPosNotify);
}
}
class BottomNavigationController extends ValueNotifier<int> {
BottomNavigationController(int value) : super(value);
}
You should use a FutureBuilder/ StreamBuilder to listen to an event after your api call is finished. Something like this:
Future<dynamic> yourApiCall() async {
// Execute your functions
return someValue;
}
Here in the UI, you can listen to the output of this function with:
FutureBuilder(
future: yourApiCall(),
builder: (context, snapshot) {
return BottomNavigation(
tabItems,
// ...other properties
selectedCallback: (int selectedPos) {
print("selected: $_selectedIndex");
if (snapshot.hasData) // Here you check if the snapshot.data is different from null. That means your api has finished and returned some value
_onNavigationBarItemTapped(selectedPos); // Only then, you notify the callback function
},
);
});