I appear to have "lost the bubble" regarding re-rendering! I'm not sure what is wrong with the current implementation of my app. It was derived with the help of a number of SO members.
What it's supposed to do: Render 9 green circles and then, one after another, render each circle in yellow in either increasing or decreasing order (Circles widget). Display the current value of count (Counter widget). In the AppBar (Home_Page widget): recognize a tap of + and increment count, recognize a tap of - and decrement count. In both cases, perform the increment/decrement in the body of a setState method. Both the Circles and the Counter widgets are expected to re-render. In this implementation, all circles participate in the green-to-yellow-to-green color changes (effectively count is ignored).
What it does: The initial rendering of the Circles and the Counter widgets display as desired. But the AppBar icons (+ and -), although recognized, do not cause a re-rendering of the Circles widget. The Counter widget does re-render its display of count. In the Circles widget is a RaisedButton that when tapped does cause the re-rendering of the Circles widget. But that button is not desired in a final implementation and is only present for testing.
What has me perplexed is that the template used for the Circles widget is the same as that used for the Counter widget. Yet they appear to execute differently.
The source code for the whole application follows. It is a single .dart file (sorry it's so long but in the past leaving something out caused questions).
Thoughts?
// ignore_for_file: camel_case_types
// ignore_for_file: constant_identifier_names
// ignore_for_file: non_constant_identifier_names
import 'package:flutter/material.dart';
import 'dart:async';
import 'dart:math';
const int NUMBER_TILES = 9;
final int CROSS_AXIS_COUNT = (sqrt(NUMBER_TILES)).toInt();
const double CROSS_AXIS_SPACING = 4.0;
const int INITIAL_COUNT = 9; // for testing; should be 1
const double MAIN_AXIS_SPACING = CROSS_AXIS_SPACING;
const int MILLISECOND_MULTIPLIER = 500;
// ************************************************************** main
void main() {
final AppState app_state = new AppState(counter: INITIAL_COUNT);
runApp(new Home_Page(app_state: app_state));
} // main
// **************************************************** class AppState
class AppState {
int counter = 0;
List<int> flash_indices = [];
bool forward = false;
AppState({this.counter}); // AppState
String toString() { // toString
return ( 'AppState{' +
'counter: $counter, ' +
'flash_indices: $flash_indices}');
} // toString
// following is a mock
void randomize_flash_indices ( ) { // randomize_flash_indices
forward = !forward;
if ( forward){
flash_indices = [ 0, 1, 2, 3, 4, 5, 6, 7, 8, -1];
}
else {
flash_indices = [ 8, 7, 6, 5, 4, 3, 2, 1, 0, -1];
}
flash_indices.add (-1); // restore to normal colors
} // randomize_flash_indices
} // class AppState
// *************************************************** class Home_Page
class Home_Page
extends StatefulWidget {
final AppState app_state;
Home_Page({
@required this.app_state,
Key key,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return Home_Page_State();
}
} // class Home_Page
// ********************************************* class Home_Page_State
class Home_Page_State extends State<Home_Page>{
Home_Page_State();
@override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Periodic',
theme: new ThemeData(primarySwatch: Colors.indigo),
home: Scaffold(
appBar: AppBar(
title: Text('Periodic'),
actions: <Widget>[
IconButton(
icon: Icon(Icons.add), // increment counter
onPressed: () {
if (widget.app_state.counter < NUMBER_TILES){
setState(() {
widget.app_state.counter++;
});
}
}
),
IconButton(
icon: Icon(Icons.remove), // decrement counter
onPressed: () {
if (widget.app_state.counter > 1){
setState(() {
widget.app_state.counter--;
});
}
}
),
]
),
body: Column(
children: [
Circles (
app_state: widget.app_state,
),
Counter (
app_state: widget.app_state,
)
],
),
),
);
} // Home_Page_State build
} // class Home_Page_State
// ***************************************************** class Circles
class Circles extends StatefulWidget {
final AppState app_state;
Circles({
@required this.app_state,
Key key,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return Circles_State();
}
} // class Circles
// *********************************************** class Circles_State
class Circles_State extends State<Circles>{
Circles_State();
int flash_tile = -1;
List<GridTile> grid_tiles = <GridTile>[];
StreamController<int> tick_controller;
StreamSubscription<int> tick_listener;
Stream<int> start_ticking() { // start_ticking
tick_controller = new StreamController();
for ( int tick = 0; (tick < widget.app_state.counter); tick++ ) {
Future.delayed(Duration(milliseconds:
MILLISECOND_MULTIPLIER * tick),() {
print('start_ticking() tick: $tick');
tick_controller.add(tick);
});
}
return tick_controller.stream;
} // start_ticking
@override
void initState() { // initState
super.initState();
widget.app_state.randomize_flash_indices();
tick_listener = start_ticking().listen(on_tick);
} // initState
@override
void dispose() { // dispose
if (tick_listener != null) {
tick_listener.cancel();
tick_listener = null;
}
super.dispose();
} // dispose
on_tick(int tick) async { // on_tick
print('listen_for_tick() tick: $tick');
this.setState(() => this.flash_tile =
widget.app_state.
flash_indices[tick]);
} // on_tick
GridTile new_circle_tile( // new_circle_tile
Color tile_color,
int index) {
GridTile tile = GridTile(
child: GestureDetector(
child: Container(
decoration: BoxDecoration(
color: tile_color,
shape: BoxShape.circle,
),
),
)
);
return (tile);
} // new_circle_tile
List<GridTile> create_circle_tiles() {// create_circle_tiles
grid_tiles = new List<GridTile>();
for (int i = 0; (i < NUMBER_TILES); i++) {
Color tile_color =
( this.flash_tile == i) ?
Colors.yellow :
Colors.green;
grid_tiles.add(new_circle_tile(tile_color, i));
}
return (grid_tiles);
} // create_circle_tiles
@override // Circles_State
Widget build(BuildContext context) {
print('Circles_State Build ' +
widget.app_state.toString() +
' flash_tile: $flash_tile');
return Column(
children: [
GridView.count(
shrinkWrap: true,
crossAxisCount: CROSS_AXIS_COUNT,
childAspectRatio: 1.0,
padding: const EdgeInsets.all(4.0),
mainAxisSpacing: MAIN_AXIS_SPACING,
crossAxisSpacing: CROSS_AXIS_SPACING,
children: create_circle_tiles(),
),
RaisedButton(
child: Text("restart"),
onPressed: () {
widget.app_state.randomize_flash_indices();
tick_listener = start_ticking().listen(on_tick);
}
),
] // children
);
} // Circles_State build
} // class Circles_State
// ***************************************************** class Counter
class Counter extends StatefulWidget {
final AppState app_state;
Counter({
@required this.app_state,
Key key,
}) : super(key: key);
@override
State<StatefulWidget> createState() {
return Counter_State();
}
} // class Counter
// *********************************************** class Counter_State
class Counter_State extends State<Counter> {
Counter_State();
@override // Counter_State
Widget build(BuildContext context) {
int counter_value = widget.app_state.counter;
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Expanded(
child: SizedBox(
width: 24.0,
child: Center(
child: Text(
'Counter $counter_value',
style: TextStyle(
color: Colors.blue,
fontWeight: FontWeight.bold,
fontSize: 24.0,
),
),
),
),
),
],
);
} // Counter_State build
} // class Counter_State