I have an MQTT sensor sending messages to the Ably platform (messaging platform) and then I am trying to subscribe to that channel and recieve the data to the app... My issue (i think) is in the getIt package as if i start my app on the dashboard page, it works, if i navigate to the page, it stays on CircularProgressIndicator(). I have removed all non-relevant code.
What is odd, is that starting the app on dashboard works everytime, but starting on homepage and navigating to the dashboard generally doesn't work, but i have had a few occurances where it does. Where it does work the build widget is called again. I have talked with Ably and don't think its an issue their side as even though the graphs aren't showing, the debug console shows that data is being recieved.
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:mre/ably_service.dart';
import 'package:mre/officeDashboardView.dart';
import 'package:mre/home_page.dart';
GetIt getIt = GetIt.instance;
void main() async {
WidgetsFlutterBinding.ensureInitialized();
getIt.registerSingletonAsync<AblyService>(() => AblyService.init());
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'B.O.B',
theme: ThemeData.light(useMaterial3: true).copyWith(
scaffoldBackgroundColor: const Color.fromARGB(255, 255, 255, 255),
appBarTheme: const AppBarTheme(
backgroundColor: Color.fromARGB(255, 235, 235, 235),
),
bottomNavigationBarTheme: const BottomNavigationBarThemeData(
//backgroundColor: Color.fromARGB(255, 235, 235, 235),
backgroundColor: Color.fromARGB(200, 200, 200, 200),
selectedItemColor: Color.fromARGB(255, 0, 0, 0),
unselectedItemColor: Color.fromARGB(255, 148, 148, 148)),
),
initialRoute: '/',
routes: {
'/': (context) => const HomePage(),
'/Office': (context) => const OfficeDashboardView(),
},
);
}
}
import 'package:flutter/material.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('B.O.B'),
actions: [
IconButton(
icon: Icon(Icons.chat),
padding: EdgeInsets.only(right: 10),
onPressed: () => Navigator.pushNamed(context, '/Office'),
)
],
centerTitle: true,
),
body: Container());
}
}
import 'dart:collection';
import 'package:mre/main.dart';
import 'package:mre/ably_service.dart';
import 'package:ably_flutter/ably_flutter.dart' as ably;
import 'package:flutter/material.dart';
import 'package:intl/intl.dart' as intl;
import 'package:syncfusion_flutter_charts/charts.dart';
class OfficeDashboardView extends StatelessWidget {
const OfficeDashboardView({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("Office Conditions", style: TextStyle(fontSize: 16)),
bottom: PreferredSize(
preferredSize: const Size.fromHeight(1.0),
child: Container(
color: const Color.fromARGB(255, 0, 0, 0),
height: 1.0,
),
),
),
body: FutureBuilder(
future: getIt.allReady(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Center(child: CircularProgressIndicator());
} else {
return const GraphsList();
}
},
),
);
}
}
class GraphsList extends StatefulWidget {
const GraphsList({
Key? key,
}) : super(key: key);
@override
_GraphsListState createState() => _GraphsListState();
}
class _GraphsListState extends State<GraphsList> {
List<DataUpdates> values = [];
@override
void initState() {
values = getIt<AblyService>().getDataUpdates();
super.initState();
}
@override
void dispose() {
print("dispose Top");
getIt<AblyService>().detachDataChannels();
//getIt<AblyService>().detachChatChannel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Center(
child: StreamBuilder<ably.ConnectionStateChange>(
stream: getIt<AblyService>().connection,
builder: (context, snapshot) {
print("Build Called");
print("SnapShot = ${snapshot}");
print("SnapShot Data = ${snapshot.hasData}");
print("SnapShot Data event = ${snapshot.data?.event}");
if (snapshot.hasData &&
snapshot.data!.event == ably.ConnectionEvent.connected) {
return SingleChildScrollView(
child: Column(
children: [
for (DataUpdates update in values)
DataGraphItem(dataUpdates: update),
],
),
);
} else if (snapshot.hasData &&
snapshot.data!.event == ably.ConnectionEvent.failed) {
return const Center(child: Text("No connection."));
} else {
return const CircularProgressIndicator();
}
},
),
);
}
}
class DataGraphItem extends StatefulWidget {
const DataGraphItem({Key? key, required this.dataUpdates}) : super(key: key);
final DataUpdates dataUpdates;
@override
DataGraphItemState createState() => DataGraphItemState();
}
class DataGraphItemState extends State<DataGraphItem> {
Queue<Data> queue = Queue();
String dataName = '';
late VoidCallback _listener;
@override
void initState() {
widget.dataUpdates.addListener(
_listener = () {
setState(() {
queue.add(widget.dataUpdates.data);
});
if (queue.length > 100) {
queue.removeFirst();
}
},
);
if (dataName.isEmpty) dataName = widget.dataUpdates.name;
super.initState();
}
@override
void dispose() {
print("Disposal");
widget.dataUpdates.removeListener(_listener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.all(15),
padding: const EdgeInsets.all(15),
height: 410,
decoration: BoxDecoration(
color: const Color(0xffEDEDED).withOpacity(0.05),
borderRadius: BorderRadius.circular(8.0)),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 500),
child: queue.isEmpty
? Center(
key: UniqueKey(),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
CircularProgressIndicator(),
SizedBox(
height: 24,
),
Text('Waiting for data...')
],
),
)
: Column(
key: ValueKey(dataName),
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
AnimatedSwitcher(
duration: const Duration(milliseconds: 200),
child: Text(
"\$${widget.dataUpdates.data.value.toStringAsFixed(2)}",
key: ValueKey(widget.dataUpdates.data.value),
style: const TextStyle(
fontSize: 20,
),
),
),
],
),
const SizedBox(height: 25),
SfCartesianChart(
enableAxisAnimation: true,
primaryXAxis: DateTimeAxis(
dateFormat: intl.DateFormat.Hms(),
intervalType: DateTimeIntervalType.minutes,
desiredIntervals: 10,
axisLine: const AxisLine(width: 2, color: Colors.white),
majorTickLines:
const MajorTickLines(color: Colors.transparent),
),
primaryYAxis: NumericAxis(
numberFormat: intl.NumberFormat('##.##'),
desiredIntervals: 5,
decimalPlaces: 2,
axisLine: const AxisLine(width: 2, color: Colors.white),
majorTickLines:
const MajorTickLines(color: Colors.transparent),
),
plotAreaBorderColor:
const Color.fromARGB(255, 0, 255, 0).withOpacity(0.2),
plotAreaBorderWidth: 0.2,
series: <LineSeries<Data, DateTime>>[
LineSeries<Data, DateTime>(
animationDuration: 0.0,
width: 2,
color: Theme.of(context).primaryColor,
dataSource: queue.toList(),
xValueMapper: (Data data, _) => data.dateTime,
yValueMapper: (Data data, _) => data.value,
)
],
)
],
),
),
);
}
}
import 'dart:async';
import 'package:mre/secrets.dart';
import 'package:ably_flutter/ably_flutter.dart' as ably;
import 'package:flutter/foundation.dart';
const List<Map> _dataTypes = [
{
"name": "Temperature",
"code": "Temp",
},
{
"name": "Humidity",
"code": "Hum",
},
];
class Data {
final String code;
final double value;
final DateTime? dateTime;
Data({
required this.code,
required this.value,
required this.dateTime,
});
}
class DataUpdates extends ChangeNotifier {
DataUpdates({required this.name});
final String name;
late Data _data;
Data get data => _data;
updateData(estimate) {
print("updateData() called with estimate: $estimate");
_data = estimate;
notifyListeners();
}
}
class AblyService {
final ably.Realtime _realtime;
List<ably.RealtimeChannel> _dataChannels = [];
Stream<ably.ConnectionStateChange> get connection =>
_realtime.connection.on();
AblyService._(this._realtime);
static Future<AblyService> init() async {
final _clientOptions = ably.ClientOptions(
key: AblyAPIKey,
clientId: "PhoneAndroid1",
logLevel: ably.LogLevel.debug);
final _realtime = ably.Realtime(options: _clientOptions);
await _realtime.connect();
return AblyService._(_realtime);
}
List<DataUpdates> _dataUpdates = [];
List<DataUpdates> getDataUpdates() {
if (_dataUpdates.isEmpty) {
for (int i = 0; i < _dataTypes.length; i++) {
String dataName = _dataTypes[i]['name'];
String dataCode = _dataTypes[i]['code'];
_dataUpdates.add(DataUpdates(name: dataName));
//launch a channel for each data type
ably.RealtimeChannel channel =
_realtime.channels.get('MQTT:Office:TempandHumidity:$dataCode');
// Add the channel to the list of channels
_dataChannels.add(channel);
//subscribe to receive channel messages
final Stream<ably.Message> messageStream = channel.subscribe();
//map each stream event to a Data and start listining
messageStream.where((event) => event.data != null).listen((message) {
print(message);
String charString =
String.fromCharCodes((message.data as Iterable<int>));
double myDouble = double.parse(charString);
_dataUpdates[i].updateData(
Data(
code: dataCode,
value: myDouble, //double.parse('${message.data}'),
dateTime: message.timestamp,
),
);
});
}
}
return _dataUpdates;
}
void detachDataChannels() {
if (_dataChannels.isNotEmpty) {
for (final channel in _dataChannels) {
channel.detach();
}
_dataChannels.clear();
}
}
}
I would like that everytime I access the dashboard view, it loads up the graphs and starts plotting the data received. As said above, if i have the dashboard as my homepage it works, but accessing it any other way it doesn't seem to recieve the ably.ConnectionEvent.connected. I can post the Debug console but aware that this post might be very long with all the code above.
Thanks.