I am trying to implement my API data in a chart using fl_chart dependencies in flutter. But I just cannot figure out how to implement it.
Here is how I implement my data:
#override
Widget build(BuildContext context) {
return ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
scrollDirection: Axis.vertical,
physics: NeverScrollableScrollPhysics(),
itemCount: 1,
itemBuilder: (context, index){
// ignore: unused_local_variable
int number = index + 1;
return Container(
width: MediaQuery.of(context).size.width * 0.50,
child: LineChart(
LineChartData(
gridData: FlGridData(
show: true,
drawVerticalLine: true,
getDrawingHorizontalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: const Color(0xff37434d),
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
bottomTitles: SideTitles(
showTitles: true,
reservedSize: 22,
getTextStyles: (value) =>
const TextStyle(color: Color(0xff68737d), fontWeight: FontWeight.bold, fontSize: 16),
getTitles: (value) {
switch (value.toInt()) {
case 2:
return 'MAR';
case 5:
return 'JUN';
case 8:
return 'SEP';
}
return '';
},
margin: 8,
),
leftTitles: SideTitles(
showTitles: true,
getTextStyles: (value) => const TextStyle(
color: Color(0xff67727d),
fontWeight: FontWeight.bold,
fontSize: 15,
),
getTitles: (value) {
switch (value.toInt()) {
case 1:
return '10k';
case 3:
return '30k';
case 5:
return '50k';
}
return '';
},
reservedSize: 28,
margin: 12,
),
),
borderData:
FlBorderData(show: true, border: Border.all(color: const Color(0xff37434d), width: 1)),
minX: 0,
maxX: 11,
minY: 0,
maxY: 6,
lineBarsData: [
LineChartBarData(
spots: [
FlSpot(0 , pings[number.toString()][index].volume),
FlSpot(2.6, 2),
FlSpot(4.9, 5),
FlSpot(6.8, 3.1),
FlSpot(8, 4),
FlSpot(9.5, 3),
FlSpot(11, 4),
],
isCurved: true,
colors: gradientColors,
barWidth: 5,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
),
belowBarData: BarAreaData(
show: true,
colors: gradientColors.map((color) => color.withOpacity(0.3)).toList(),
),
),
],
)
And here is how i call my data:
Map<String, List<TankPing>> pings;
initState() {
Services.fetchPing().then((tankPings) => {
setState((){
pings = tankPings;
})
});
super.initState();
}
My API call is in another file. I call the API like below:
static Future<Map<String, List<TankPing>>> fetchPing() async {
String url3 = 'https://api.orbital.katsana.com/devices/graph-data';
Future<SharedPreferences> _prefs = SharedPreferences.getInstance();
final SharedPreferences prefs = await _prefs;
final token = prefs.getString('access_token');
final response3 = await http.get(url3, headers: {
'Authorization': 'Bearer $token'
});
if(response3.statusCode == 200) {
final tankPings = tankPingFromJson(response3.body);
return tankPings;
}else if(response3.statusCode == 400) {
print('Connection to server is bad');
}else if(response3.statusCode == 500){
print('No authorization');
}
}
I am trying to implement it inside of FlSPot() function. But then U receive this error:
The method '[]' was called on null.
Receiver: null
Tried calling: []("1")
Here is my model:
import 'dart:convert';
Map<String, List<TankPing>> tankPingFromJson(dynamic str) => Map.from(json.decode(str)).map((k, v) => MapEntry<String, List<TankPing>>(k, List<TankPing>.from(v.map((x) => TankPing.fromJson(x)))));
String tankPingToJson(Map<String, List<TankPing>> data) => json.encode(Map.from(data).map((k, v) => MapEntry<String, dynamic>(k, List<dynamic>.from(v.map((x) => x.toJson())))));
class TankPing {
TankPing({
this.trackedAt,
this.fuel,
this.level,
this.volume,
});
DateTime trackedAt;
double fuel;
double level;
double volume;
factory TankPing.fromJson(Map<String, dynamic> json) => TankPing(
trackedAt: DateTime.parse(json["tracked_at"]),
fuel: json["fuel"].toDouble(),
level: json["level"].toDouble(),
volume: json["volume"].toDouble(),
);
Map<String, dynamic> toJson() => {
"tracked_at": trackedAt.toString(),
"fuel": fuel,
"level": level,
"volume": volume,
};
}
Here is how the API look:
{
"1": [
{
"tracked_at": "2020-11-20T19:41:21.000000Z",
"fuel": 87.03,
"level": 3.0460554,
"volume": 50665.14
},
{
"tracked_at": "2020-11-22T00:19:41.000000Z",
"fuel": 85.75,
"level": 3.0012249,
"volume": 50051.86
},
{
"tracked_at": "2020-11-22T00:32:00.000000Z",
"fuel": 84.17,
"level": 2.9460489,
"volume": 49265.04
},
]
My API is very long and it looks like that. Any help would be appreciated.
I just post the code example in here. If you have any question, you can ask me and I will try to answer the question I can because this code is like almost 2 or 3 years old now and I did not work on this project anymore. Hope the code below helps you!
import 'package:charts_flutter/flutter.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:intl/intl.dart';
import 'custom_symbol_renderer.dart';
import 'package:orbital_app/Model/tank_ping.dart';
import 'package:orbital_app/Provider/api_provider.dart';
class TankChart extends StatefulWidget {
//This is my API class object to extract the data
TankChart({Key key}) : super(key: key);
#override
_TankChartState createState() => _TankChartState();
}
class _TankChartState extends State<TankChart> {
var ping;
var tankInfo;
// Since I am using a Provider in this code, I call the API here
getPingProvider(){
setState((){
ping = Provider.of<TankPingProvider>(context, listen: false);
ping.getTankPing(context);
});
}
getInfoProvider(){
setState((){
tankInfo = Provider.of<TankInfoProvider>(context, listen: false);
tankInfo.getTankInfo(context);
});
}
#override
initState() {
super.initState();
getPingProvider();
getInfoProvider();
}
#override
Widget build(BuildContext context) {
// Here I format the time to normal human time
final numericFormatter = charts.BasicNumericTickFormatterSpec.fromNumberFormat(
NumberFormat.compact()
);
final ping = Provider.of<TankPingProvider>(context);
return ListView.builder(
padding: EdgeInsets.zero,
// Here I want everything to be shrink and expand when the user needs it
shrinkWrap: true,
// Here is where I set whether the graph can be expand by user vertical
// scroll
physics: NeverScrollableScrollPhysics(),
//The data from the API is here
itemCount: ping.tankPing.length,
itemBuilder: (context, index){
if(ping.tankPing.length == null){
return CircularProgressIndicator();
} else if(ping.tankPing == null){
return CircularProgressIndicator();
} else{
int no = index + 1;
final size = MediaQuery.of(context).size;
// Here is the API dot or data dot on the graph
List<charts.Series<TankPing, DateTime>> series = [
charts.Series(
id: '${tankInfo.tankInfos.data[index].name}',
data: ping.tankPing[no.toString()],
colorFn: (_, __) => MaterialPalette.blue.shadeDefault,
domainFn: (TankPing ping, _) => ping.trackedAt,
measureFn: (TankPing ping, _) => ping.volume
),
];
return Container(
height: 250,
child: Card(
child: Column(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.only(
left: 5
),
child: charts.TimeSeriesChart(
series,
animate: false,
domainAxis: charts.DateTimeAxisSpec(
tickFormatterSpec: charts.AutoDateTimeTickFormatterSpec(
day: charts.TimeFormatterSpec(
format: 'dd',
transitionFormat: 'dd MMM',
),
),
),
primaryMeasureAxis: charts.NumericAxisSpec(
tickFormatterSpec: numericFormatter,
renderSpec: charts.GridlineRendererSpec(
// Tick and Label styling here.
labelStyle: charts.TextStyleSpec(
fontSize: 10, // size in Pts.
color: charts.MaterialPalette.black
),
)
),
defaultRenderer: charts.LineRendererConfig(
includeArea: true,
includeLine: true,
includePoints: true,
strokeWidthPx: 0.5,
radiusPx: 1.5
),
dateTimeFactory: const charts.LocalDateTimeFactory(),
behaviors: [
charts.SlidingViewport(),
charts.PanAndZoomBehavior(),
charts.SeriesLegend(
position: charts.BehaviorPosition.top,
horizontalFirst: false,
cellPadding: EdgeInsets.only(
left: MediaQuery.of(context).size.width * 0.27,
top: 15
),
),
charts.SelectNearest(
eventTrigger: charts.SelectionTrigger.tap
),
charts.LinePointHighlighter(
symbolRenderer: CustomCircleSymbolRenderer(size: size),
),
],
selectionModels: [
charts.SelectionModelConfig(
type: charts.SelectionModelType.info,
changedListener: (charts.SelectionModel model) {
if(model.hasDatumSelection) {
final tankVolumeValue = model.selectedSeries[0].measureFn(model.selectedDatum[0].index).round();
final dateValue = model.selectedSeries[0].domainFn(model.selectedDatum[0].index);
CustomCircleSymbolRenderer.value = '$dateValue \n $tankVolumeValue L';
}
})
]),
),
),
],
),
),
);
}
});
}
}
The answer is use the min and max value to determine how long the data will be. And then just use the flSpot to enter your data.
Related
I am developing an application in which user can contact each other via video calls. I have setup my server on railway by following the Agora documentation. If anyone has any suggestions or know what I am doing wrong please do let me know. I have tried leaving the token empty ('') but it still gives invalid token error. The token is getting generated successfully but when users join the call. Remote view is not showing up even though the onUserJoined callback is getting triggered perfectly on both caller and receiver side.
This is the call screen code which will enable users to contact with each other
// ignore_for_file: prefer_typing_uninitialized_variables, use_build_context_synchronously
import 'dart:async';
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../../constants/constants.dart';
import '../../global/firebase_ref.dart';
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:wakelock/wakelock.dart';
import '../../methods/call_methods.dart';
import '../../models/call_model.dart';
import '../../services/app_services.dart';
import '../../services/connectivity_services.dart';
import '../../services/user_services.dart';
import '../../widgets/custom_images.dart';
import '../../widgets/custom_widgets.dart';
class VideoCallScreen extends StatefulWidget {
const VideoCallScreen(this.call, {Key? key}) : super(key: key);
final CallModel call;
#override
State<VideoCallScreen> createState() => _VideoCallScreenState();
}
class _VideoCallScreenState extends State<VideoCallScreen> {
final _users = <int>[];
final _infoStrings = <String>[];
bool muted = false;
RtcEngine? _engine;
bool isspeaker = true;
bool isalreadyendedcall = false;
String current = Get.find<UserServices>().adminid;
CollectionReference? reference;
String token = '';
Stream<DocumentSnapshot>? stream;
#override
void dispose() {
_users.clear();
_engine!.leaveChannel();
_engine!.release();
super.dispose();
}
getToken() async {
final url = Uri.parse(
'https://agora-token-service-production-59a1.up.railway.app/rtc/${widget.call.channelid}/1/uid/0',
);
Get.log('Token URL $url');
final response = await http.get(url);
debugPrint('Response: $response');
if (response.statusCode == 200) {
setState(() {
token = response.body;
token = jsonDecode(token)['rtcToken'];
Get.log('token: $token');
});
} else {
Get.log('Failed to fetch the token');
}
}
#override
void initState() {
initialize();
super.initState();
if (widget.call.by == current) {
reference = userRef.doc(widget.call.receiver!.id).collection('History');
stream = reference!.doc(widget.call.timeepoch.toString()).snapshots();
} else {
reference = adminRef.doc(widget.call.caller!.id).collection('History');
stream = reference!.doc(widget.call.timeepoch.toString()).snapshots();
}
}
Future<void> initialize() async {
try {
await [Permission.microphone, Permission.camera].request();
await getToken();
if (Get.find<AppServices>().appid.isEmpty) {
setState(() {
_infoStrings.add(
'Agora_APP_IDD missing, please provide your Agora_APP_IDD in app_constant.dart',
);
_infoStrings.add('Agora Engine is not starting');
});
return;
}
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
VideoEncoderConfiguration configuration = const VideoEncoderConfiguration(
dimensions: VideoDimensions(height: 1920, width: 1080),
);
await _engine!.setVideoEncoderConfiguration(configuration);
Get.log('Channel id: ${widget.call.channelid}');
await _engine!.joinChannel(
token: token,
channelId: widget.call.channelid!,
uid: 0,
options: const ChannelMediaOptions(),
);
} catch (e) {
Get.log('Catch: $e');
}
}
Future<void> _initAgoraRtcEngine() async {
_engine = createAgoraRtcEngine();
await _engine!.initialize(
RtcEngineContext(
appId: Get.find<AppServices>().appid,
channelProfile: ChannelProfileType.channelProfileCommunication,
),
);
// _engine = await RtcEngine.create(Get.find<AppServices>().agoraid);
await _engine!.enableVideo();
await _engine!.enableAudio();
await _engine!.enableLocalVideo(true);
await _engine!.enableLocalAudio(true);
await _engine!.setClientRole(role: ClientRoleType.clientRoleBroadcaster);
Get.log('---engine----');
}
var remoteid;
void _addAgoraEventHandlers() {
_engine!.registerEventHandler(
RtcEngineEventHandler(
onError: (code, value) {
setState(() {
final info = 'onErrorCode: $code';
_infoStrings.add(info);
Get.log(info);
final infp = 'onError: $value';
_infoStrings.add(infp);
Get.log(infp);
});
},
onJoinChannelSuccess: (channel, elapsed) {
setState(() {
final info = 'onJoinChannel: $channel';
_infoStrings.add(info);
Get.log(info);
});
if (widget.call.caller!.id == current) {
adminRef
.doc(widget.call.caller!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'TYPE': 'OUTGOING',
'ISVIDEOCALL': widget.call.video,
'PEER': widget.call.receiver!.id,
'TARGET': widget.call.receiver!.id,
'TIME': widget.call.timeepoch,
'DP': widget.call.receiver!.picture,
'ISMUTED': false,
'ISJOINEDEVER': false,
'STATUS': 'calling',
'STARTED': null,
'ENDED': null,
'CALLERNAME': widget.call.caller!.name,
'CHANNEL': channel.channelId,
'UID': channel.localUid,
}, SetOptions(merge: true)).then(
(value) => Get.log('added'),
);
userRef
.doc(widget.call.receiver!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'TYPE': 'INCOMING',
'ISVIDEOCALL': widget.call.video,
'PEER': widget.call.caller!.id,
'TARGET': widget.call.receiver!.id,
'TIME': widget.call.timeepoch,
'DP': widget.call.caller!.picture,
'ISMUTED': false,
'ISJOINEDEVER': true,
'STATUS': 'missedcall',
'STARTED': null,
'ENDED': null,
'CALLERNAME': widget.call.caller!.name,
'CHANNEL': channel.channelId,
'UID': channel.localUid,
}, SetOptions(merge: true));
}
Wakelock.enable();
},
onLeaveChannel: (connection, stats) {
// setState(() {
_infoStrings.add('onLeaveChannel');
_users.clear();
// });
if (isalreadyendedcall == false) {
adminRef
.doc(widget.call.caller!.id!)
.collection("History")
.doc(widget.call.timeepoch.toString())
.set({
'STATUS': 'ended',
'ENDED': DateTime.now(),
'ISMUTED': false,
'UID': -1,
}, SetOptions(merge: true));
userRef
.doc(widget.call.receiver!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'STATUS': 'ended',
'ENDED': DateTime.now(),
'ISMUTED': false,
'UID': -1,
}, SetOptions(merge: true));
// //----------
// userRef
// .doc(widget.call.receiver!.id)
// .collection('recent')
// .doc(widget.call.id)
// .set({
// 'id': widget.call.caller!.id,
// 'ENDED': DateTime.now().millisecondsSinceEpoch,
// 'CALLERNAME': widget.call.receiver!.name,
// }, SetOptions(merge: true));
}
Wakelock.disable();
},
onUserJoined: (connection, uid, elapsed) {
setState(() {
final info = 'userJoined: $uid';
_infoStrings.add(info);
_users.add(uid);
Get.log(info);
remoteid = uid;
Get.log(remoteid);
});
startTimerNow();
if (Get.find<UserServices>().adminid == widget.call.caller!.id) {
adminRef
.doc(widget.call.caller!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'STARTED': DateTime.now(),
'STATUS': 'pickedup',
'ISJOINEDEVER': true,
}, SetOptions(merge: true));
userRef
.doc(widget.call.receiver!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'STARTED': DateTime.now(),
'STATUS': 'pickedup',
}, SetOptions(merge: true));
}
Wakelock.enable();
},
onUserOffline: (connection, uid, elapsed) {
setState(() {
final info = 'userOffline: $uid';
_infoStrings.add(info);
_users.remove(uid);
Get.log(info);
remoteid = null;
});
if (isalreadyendedcall == false) {
adminRef
.doc(widget.call.caller!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'STATUS': 'ended',
'ENDED': DateTime.now(),
'ISMUTED': false,
'UID': -1,
}, SetOptions(merge: true));
userRef
.doc(widget.call.receiver!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'STATUS': 'ended',
'ENDED': DateTime.now(),
'ISMUTED': false,
'UID': -1,
}, SetOptions(merge: true));
//----------
// userRef
// .doc(widget.call.receiver!.id)
// .collection('recent')
// .doc(widget.call.id)
// .set({
// 'id': widget.call.caller!.id,
// 'ENDED': DateTime.now().millisecondsSinceEpoch,
// 'CALLERNAME': widget.call.receiver!.name,
// }, SetOptions(merge: true));
}
},
onFirstRemoteVideoFrame: (connection, uid, width, height, elapsed) {
setState(() {
final info = 'firstRemoteVideo: $uid ${width}x $height';
_infoStrings.add(info);
Get.log(info);
});
},
onTokenPrivilegeWillExpire: (connection, string) async {
await getToken();
await _engine!.renewToken(token);
},
),
);
}
void onCallEnd(BuildContext context) async {
await CallMethods.endCall(call: widget.call);
DateTime now = DateTime.now();
if (isalreadyendedcall == false) {
await adminRef
.doc(widget.call.caller!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'STATUS': 'ended',
'ENDED': now,
'ISMUTED': false,
"UID": -1,
}, SetOptions(merge: true));
await userRef
.doc(widget.call.receiver!.id!)
.collection('History')
.doc(widget.call.timeepoch.toString())
.set({
'STATUS': 'ended',
'ENDED': now,
'ISMUTED': false,
'UID': -1,
}, SetOptions(merge: true));
// //----------
// userRef
// .doc(widget.call.receiver!.id)
// .collection('recent')
// .doc(widget.call.id)
// .set({
// 'id': widget.call.caller!.id,
// 'ENDED': DateTime.now().millisecondsSinceEpoch,
// 'CALLERNAME': widget.call.receiver!.name,
// }, SetOptions(merge: true));
}
Wakelock.disable();
Navigator.pop(context);
}
Widget callView({
String status = 'calling',
bool muted = false,
int? remoteuid,
}) {
var w = MediaQuery.of(context).size.width;
var h = MediaQuery.of(context).size.height;
return Container(
alignment: Alignment.center,
decoration: status == 'pickedup'
? null
: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: providerImage(
widget.call.caller!.id == current
? widget.call.receiver!.picture ?? ''
: widget.call.caller!.picture ?? '',
),
),
),
child: Container(
color: status == 'pickedup' ? null : Colors.white.withOpacity(0.3),
child: Stack(
alignment: Alignment.center,
children: [
status != 'pickedup'
? Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.start,
children: [
Container(
width: w,
height: h / 5,
alignment: Alignment.center,
margin: EdgeInsets.only(
top: MediaQuery.of(context).padding.top),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: const [
Icon(
Icons.lock_rounded,
size: 17,
color: Colors.white38,
),
SizedBox(width: 6),
Text(
'End-to-end encrypted',
style: TextStyle(
color: Colors.white38,
fontWeight: FontWeight.w400,
fontFamily: AppStrings.opensans,
),
),
],
).marginOnly(top: 50, bottom: 7),
SizedBox(
width: w / 1.1,
child: Text(
widget.call.caller!.id ==
Get.find<UserServices>().adminid
? widget.call.receiver!.name!
: widget.call.caller!.name!,
maxLines: 1,
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 27,
fontFamily: AppStrings.opensans,
),
),
),
],
),
),
Text(
status == 'calling'
? widget.call.receiver!.id ==
Get.find<UserServices>().adminid
? 'Connecting...'
: 'Calling...'
: status == 'pickedup'
? '$hoursStr : $minutesStr: $secondsStr'
: status == 'ended'
? 'Call Ended'
: status == 'rejected'
? 'Rejected'
: 'Please wait...',
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 18,
fontFamily: AppStrings.opensans,
),
).marginOnly(bottom: 16, top: 10),
Stack(
children: [
widget.call.caller!.id ==
Get.find<UserServices>().adminid
? status == 'ended' || status == 'rejected'
? Container(
height: w + (w / 11),
width: w,
color: Colors.white12,
child: Icon(
status == 'ended'
? Icons.person_off
: status == 'rejected'
? Icons.call_end_rounded
: Icons.person,
size: 140,
),
)
: Container()
: status == 'ended' || status == 'rejected'
? Container(
height: w + (w / 11),
width: w,
color: Colors.white12,
child: Icon(
status == 'ended'
? Icons.person_off
: status == 'rejected'
? Icons.call_end_rounded
: Icons.person,
size: 140,
),
)
: Container(),
Positioned(
bottom: 20,
child: SizedBox(
width: w,
height: 20,
child: Center(
child: status == 'pickedup'
? muted == true
? const Text(
'Muted',
textAlign: TextAlign.center,
style: TextStyle(
fontWeight: FontWeight.w600,
fontSize: 16,
fontFamily: AppStrings.opensans,
),
)
: const SizedBox(height: 0)
: const SizedBox(height: 0),
),
),
),
],
),
SizedBox(height: h / 6),
],
)
: _engine == null
? SizedBox()
: SizedBox(
child: AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine!,
canvas: VideoCanvas(uid: remoteuid),
connection: RtcConnection(
channelId: widget.call.channelid!,
),
),
),
),
if (status == 'pickedup')
Positioned(
top: 150,
child: Text(
'$hoursStr: $minutesStr: $secondsStr',
style: const TextStyle(
fontWeight: FontWeight.w500,
fontSize: 18,
color: Colors.white,
fontFamily: AppStrings.opensans,
),
),
),
if (status != 'ended' || status != 'rejected')
_engine == null
? SizedBox()
: Align(
alignment: Alignment.bottomRight,
child: SizedBox(
width: 200,
height: 200,
child: AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine!,
canvas: const VideoCanvas(uid: 0),
),
),
),
),
],
),
),
);
}
onToggleMute() {
setState(() {
muted = !muted;
});
_engine!.muteLocalAudioStream(muted);
reference!
.doc(widget.call.timeepoch.toString())
.set({'ISMUTED': muted}, SetOptions(merge: true));
}
onSwitchCamera() => setState(() => _engine!.switchCamera());
Widget toolbar({String status = 'calling'}) {
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 35),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
status != 'ended' && status != 'rejected'
? SizedBox(
width: 65.67,
child: RawMaterialButton(
onPressed: onToggleMute,
shape: const CircleBorder(),
elevation: 2.0,
fillColor: muted ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
child: Icon(
muted ? Icons.mic_off : Icons.mic,
color: muted ? Colors.white : Colors.blueAccent,
size: 22.0,
),
),
)
: const SizedBox(height: 42, width: 65.67),
SizedBox(
width: 65.67,
child: RawMaterialButton(
onPressed: () async {
Get.log('--on call end---');
setState(() {
isalreadyendedcall =
status == 'ended' || status == 'rejected' ? true : false;
onCallEnd(context);
});
},
shape: const CircleBorder(),
elevation: 2.0,
fillColor: status == 'ended' || status == 'rejected'
? Colors.black
: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
child: Icon(
status == 'ended' || status == 'rejected'
? Icons.close
: Icons.call,
color: Colors.white,
size: 35.0,
),
),
),
status == 'ended' || status == 'rejected'
? const SizedBox(width: 65.67)
: SizedBox(
width: 65.67,
child: RawMaterialButton(
onPressed: onSwitchCamera,
shape: const CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(12.0),
child: const Icon(
Icons.switch_camera,
color: Colors.blueAccent,
size: 20.0,
),
),
),
],
),
);
}
Widget panel() {
return Container(
padding: const EdgeInsets.symmetric(vertical: 48),
alignment: Alignment.bottomCenter,
child: FractionallySizedBox(
heightFactor: 0.5,
child: Container(
padding: const EdgeInsets.symmetric(vertical: 48),
child: ListView.builder(
reverse: true,
itemCount: _infoStrings.length,
itemBuilder: (BuildContext context, int index) {
if (_infoStrings.isEmpty) return const SizedBox();
return Padding(
padding:
const EdgeInsets.symmetric(vertical: 3, horizontal: 10),
child: Text(_infoStrings[index]),
);
},
),
),
),
);
}
#override
Widget build(BuildContext context) {
return Obx(
() => Get.find<ConnectivityService>().connectionStatus.value ==
ConnectivityResult.none
? const DisconnectedWidget()
: Scaffold(
body: Stack(
children: [
_engine == null
? Center(
child: Stack(
children: [callView(), panel(), toolbar()],
),
)
: StreamBuilder<DocumentSnapshot>(
stream: stream as Stream<DocumentSnapshot>,
builder: (context, snapshot) {
if (snapshot.hasData &&
snapshot.data!.data() != null &&
snapshot.data != null) {
var doc = snapshot.data!;
Get.log(doc.toString());
return Center(
child: Stack(
children: [
callView(
status: doc['STATUS'],
muted: doc['ISMUTED'],
remoteuid: doc['UID'],
),
panel(),
toolbar(status: doc['STATUS']),
],
),
);
}
return Center(
child: Stack(
children: [callView(), panel(), toolbar()],
),
);
},
),
],
),
),
);
}
}
I didn't set it up with a rails server but it should work the same. On the flutter side im calling a function at the very first point:
final token = await createToken(channelName, userId);
channel name to identify the channel for the users and a userId of my user who should be able to join the channel.
Future<dynamic> createToken(String channelName, int uid) async {
try {
//404
// final response = await dio.get('${url}/api/video/create-token?agChannelName=$channelName&agRole=$role&agUid=$uid&agExpireTime=$expireTime');
final response = await dio.get(
'${url}/api/video/create-token?agChannelName=$channelName&agUid=$uid');
print('res is ${response.data["token"]}');
return response.data["token"];
} on DioError catch (e) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx and is also not 304.
if (e.response != null) {
//print(HttpException(e.response!.data["message"]));
return e.response!.data;
//print(e.response!.headers);
//print(e.response!.requestOptions);
} else {
// Something happened in setting up or sending the request that triggered an Error
//print(e.requestOptions);
print('get events: ${e.message}');
}
}
}
On my server side where im using a javascript framework, im doing the following:
...
const token = RtcTokenBuilder.buildTokenWithUid(
process.env.AGORA_APP_ID,
process.env.AGORA_APP_CERTIFICATE,
channelName,
uid,
RtcRole.PUBLISHER,
privilegeExpireTime
);
console.log(token)
return res.status(201).json({ token: token });
For that im using the agora-access-token library on npm https://www.npmjs.com/package/agora-access-token
I want to show fl line chart using this plugin https://pub.dev/packages/fl_chart.
data is double value and date in format of yyyy-MM-dd. Now I tried to show this data in chart.
Y axis showing data correctly but date calculations are not may be correct it showing lots of dates.
and If I add minX and maxX app got stuck due to lots of dates calculation between min and max X date values.
this is my code: in this data x is Date value and Y is double value.
I got the idea from this link https://github.com/imaNNeoFighT/fl_chart/issues/471
class AnalyticsChart extends StatefulWidget {
const AnalyticsChart({Key? key}) : super(key: key);
#override
_AnalyticsChartState createState() => _AnalyticsChartState();
}
class _AnalyticsChartState extends State<AnalyticsChart> {
List<Color> gradientColors = [
const Color(0xff099f53),
const Color(0xfffefffe),
];
bool showAvg = false;
final dateList = [];
#override
Widget build(BuildContext context) {
double windowHeight = MediaQuery.of(context).size.height - 370;
return Container(
height: windowHeight,
decoration: const BoxDecoration(
borderRadius: BorderRadius.all(
Radius.circular(0),
),
color: whiteColor),
child: Padding(
padding:
const EdgeInsets.only(right: 18.0, left: 12.0, top: 15, bottom: 12),
child: BlocBuilder<AnalyticsChartBloc, AnalyticsChartState>(
builder: (BuildContext context, AnalyticsChartState state) {
if (state is LoadAnalyticsStatesState) {
if (state.totalSales != null &&
(state.totalSales?.isNotEmpty ?? false)) {
List<FlSpot>? flSpots = [];
dateList.clear();
flSpots = state.totalSales?.map((e) {
dateList.add(e.x);
return FlSpot(
DateTime.parse(e.x ?? '') // e.date = "2020-10-24"
.millisecondsSinceEpoch
.toDouble(),
double.parse(e.y ?? '0'));
}).toList();
return LineChart(
mainData(flSpots),
);
}
}
return _buildNoChart();
}),
),
);
}
Widget bottomTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
height: 1,
color: placeholderColor,
fontWeight: weight500,
fontSize: fontsize12,
);
final DateTime date = DateTime.fromMillisecondsSinceEpoch(value.toInt());
final parts = date.toIso8601String().split("T");
return SideTitleWidget(
space: 12,
child: Text(parts.first, style: style),
axisSide: meta.axisSide,
);
}
Widget leftTitleWidgets(double value, TitleMeta meta) {
const style = TextStyle(
color: placeholderColor,
fontWeight: weight100,
fontSize: fontsize9,
);
return Container(
padding: const EdgeInsets.only(right: 10),
height: 50.0,
child: Text(
value.toStringAsFixed(0),
style: style,
));
}
LineChartData mainData(List<FlSpot>? flSpots) {
final List<double> xList = [];
final List<double> yList = [];
flSpots?.map((e) {
xList.add(e.x);
yList.add(e.y);
}).toList();
final minX = xList.reduce(min); //
final maxX = xList.reduce(max); //
final minY = yList.reduce(min); //
final maxY = ((yList.reduce(max)) + 1000).toInt().toDouble(); //
debugPrint('minX/maxX $minX $maxX ');
debugPrint('minY/maxY $minY $maxY ');
debugPrint('maxX-minX ${maxX - minX} ');
return LineChartData(
lineTouchData: LineTouchData(
enabled: true,
touchTooltipData: LineTouchTooltipData(
tooltipBgColor: primaryColorGreen,
tooltipRoundedRadius: 4.0,
showOnTopOfTheChartBoxArea: false,
fitInsideHorizontally: true,
getTooltipItems: (touchedSpots) {
return touchedSpots.map(
(LineBarSpot touchedSpot) {
const textStyle = TextStyle(
fontSize: fontsize13,
fontWeight: weight500,
color: whiteColor,
height: 1.4,
);
return LineTooltipItem(
'Total Sales \n 17,500',
textStyle,
);
},
).toList();
},
),
),
gridData: FlGridData(
show: true,
drawVerticalLine: true,
horizontalInterval: 1,
verticalInterval: 1,
getDrawingHorizontalLine: (value) {
return FlLine(
color: borderColor,
strokeWidth: 1,
);
},
getDrawingVerticalLine: (value) {
return FlLine(
color: Colors.transparent,
strokeWidth: 1,
);
},
),
titlesData: FlTitlesData(
show: true,
rightTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
topTitles: AxisTitles(
sideTitles: SideTitles(showTitles: false),
),
bottomTitles: AxisTitles(
sideTitles: SideTitles(
showTitles: true,
interval: (flSpots![flSpots.length - 1].x - flSpots[0].x),
getTitlesWidget: bottomTitleWidgets,
))),
maxX: maxX,
minX: minX,
maxY: maxY,
minY: minY,
borderData: FlBorderData(
show: true, border: Border.all(color: Colors.transparent, width: 0)),
lineBarsData: [
LineChartBarData(
spots: flSpots.asMap().entries.map((e) {
return FlSpot(e.key.toDouble(), e.value.y);
}).toList(),
isCurved: false,
color: primaryColorGreen,
barWidth: 2,
isStrokeCapRound: true,
dotData: FlDotData(
show: true,
),
belowBarData: BarAreaData(
show: true,
gradient: LinearGradient(
colors: gradientColors
.map((color) => color.withOpacity(0.3))
.toList(),
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
),
],
);
}
Widget _buildNoChart() => const SizedBox(
child: Center(
child: Text('No chart data found'),
),
);
}
///////////////////////
I am working on project which requires to add bar chart in the pdf(if possible customizable pdf). I am trying to add using below code but it didn't work
void _printScreen() {
doc.addPage(pw.MultiPage(
pageFormat: PdfPageFormat.letter
.copyWith(marginBottom: 1.5 * PdfPageFormat.cm),
header: (pw.Context context) {
if (context.pageNumber == 1) {
return null;
}
return pw.Container(
alignment: pw.Alignment.centerRight,
margin:
const pw.EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
padding:
const pw.EdgeInsets.only(bottom: 3.0 * PdfPageFormat.mm),
decoration: const pw.BoxDecoration(
border: pw.Border(
bottom: pw.BorderSide(
color: PdfColors.grey,
width: 0.5,
)),
),
child: pw.Text('Report',
style: pw.Theme.of(context)
.defaultTextStyle
.copyWith(color: PdfColors.grey)));
},
footer: (pw.Context context) {
return pw.Container(
alignment: pw.Alignment.centerRight,
margin: const pw.EdgeInsets.only(top: 1.0 * PdfPageFormat.cm),
child: pw.Text(
'Page ${context.pageNumber} of ${context.pagesCount}',
style: pw.Theme.of(context)
.defaultTextStyle
.copyWith(color: PdfColors.grey)));
},
build: (pw.Context context) => [
pw.Center(
child: pw.Expanded(
child: pw.Image(image),
),
)
]));
//save PDF
final String dir = (await getExternalStorageDirectory()).path;
final String path =
'$dir/myreport${DateTime.now().toIso8601String()}.pdf';
final File file = File(path);
await file.writeAsBytes(await doc.save());
snackbarMessage(
message: "Report Saved Successfully.",
icon: FontAwesomeIcons.infoCircle);
return doc.save();
});
}
if anyone implemeted something like this(Adding graph in pdf) please share your answer..Thank you in advance :)
Hello I actually figure it out by adding an "image" of the chat i want inside the pdf. You need these packages to accomplish that
flutter_email_sender: ^5.0.2
fl_chart: ^0.46.0
screenshot: ^1.2.3
pdf: ^3.3.0
path_provider: ^2.0.9
permission_handler: ^9.2.0
Some of them are needed for my example below. My example creates a pdf (saves it to the device) and opens the email to sent it to a user.
you are also going to need these permissions in your manifest
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
pdf_page.dart
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:path_provider/path_provider.dart';
import 'package:pdf/pdf.dart';
import 'package:pdf/widgets.dart' as pw;
import 'dart:io';
import 'package:flutter_email_sender/flutter_email_sender.dart';
import 'package:screenshot/screenshot.dart';
import 'package:test_project_pdf/chart_for_pdf.dart';
class PdfPage extends StatelessWidget {
const PdfPage({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return TextButton(
onPressed: () async {
if (await Permission.storage.isGranted) {
await _pdfResults(context);
} else {
await [Permission.storage].request();
if (await Permission.storage.isGranted) {
await _pdfResults(context);
} else {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Need permissions"),
));
}
}
},
child: Center(
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(32)),
child: const Text(
"Send e-mail",
style: TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18),
)),
),
);
}
Future _pdfResults(context) async {
var pdf = pw.Document();
ScreenshotController screenshotController = ScreenshotController();
final bytes = await screenshotController.captureFromWidget(MediaQuery(data: const MediaQueryData(), child: ChartForPdf()));
pdf.addPage(
pw.MultiPage(
pageFormat: PdfPageFormat.a4,
margin: const pw.EdgeInsets.all(32),
build: (pw.Context context) {
return <pw.Widget>[
pw.Column(
children: [
pw.Center(
child: pw.Container(
height: 700,
width: 1080,
child: pw.Expanded(
child: pw.Image(pw.MemoryImage(bytes)),
),
),
),
],
),
];
},
),
);
await savePdf(pdf, context);
}
Future<String> savePdf(pw.Document pdf, var context) async {
var path;
late File file;
Directory directory;
if (Platform.isAndroid) {
path = (await getExternalStorageDirectory())!.path;
file = File("$path/best_pdf.pdf");
} else if (Platform.isIOS) {
path = await getApplicationDocumentsDirectory();
directory = await Directory("${path.path}/best_pdfs").create();
file = File("${directory.path}/best_pdf.pdf");
}
if (await file.exists()) {
try {
await file.delete();
} on Exception catch (e) {
print(e);
}
}
await file.writeAsBytes(await pdf.save());
await send(file.path, context);
return file.path;
}
Future<void> send(String path, var context) async {
final Email email = Email(
subject: "Mood Chart",
recipients: [""],
attachmentPaths: [path],
isHTML: false,
);
try {
await FlutterEmailSender.send(email);
// platformResponse = 'Επιτυχία';
} on PlatformException catch (error) {
if (error.message == "No email clients found!") {
ScaffoldMessenger.of(context).showSnackBar(const SnackBar(
content: Text("Device has no email"),
duration: Duration(seconds: 2)));
}
}
}
}
chart_for_pdf.dart
import 'dart:math';
import 'package:fl_chart/fl_chart.dart';
import 'package:flutter/material.dart';
class ChartForPdf extends StatelessWidget {
final Color barBackgroundColor = const Color(0xff72d8bf);
final Duration animDuration = const Duration(milliseconds: 250);
int touchedIndex = -1;
#override
Widget build(BuildContext context) {
return AspectRatio(
aspectRatio: 1,
child: Card(
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(18)),
color: const Color(0xff81e5cd),
child: Stack(
children: <Widget>[
Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
const Text(
'Mingguan',
style: TextStyle(
color: Color(0xff0f4a3c),
fontSize: 24,
fontWeight: FontWeight.bold),
),
const SizedBox(
height: 4,
),
const Text(
'Grafik konsumsi kalori',
style: TextStyle(
color: Color(0xff379982),
fontSize: 18,
fontWeight: FontWeight.bold),
),
const SizedBox(
height: 38,
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: BarChart( mainBarData(),
swapAnimationDuration: animDuration,
),
),
),
const SizedBox(
height: 12,
),
],
),
),
],
),
),
);
}
BarChartGroupData makeGroupData(
int x,
double y, {
bool isTouched = false,
Color barColor = Colors.white,
double width = 22,
List<int> showTooltips = const [],
}) {
return BarChartGroupData(
x: x,
barRods: [
BarChartRodData(
toY: isTouched ? y + 1 : y,
colors: isTouched ? [Colors.yellow] : [barColor],
width: width,
borderSide: isTouched
? BorderSide(color: Colors.yellow, width: 1)
: const BorderSide(color: Colors.white, width: 0),
backDrawRodData: BackgroundBarChartRodData(
show: true,
toY: 20,
colors: [barBackgroundColor],
),
),
],
showingTooltipIndicators: showTooltips,
);
}
List<BarChartGroupData> showingGroups() => List.generate(7, (i) {
switch (i) {
case 0:
return makeGroupData(0, 5, isTouched: i == touchedIndex);
case 1:
return makeGroupData(1, 6.5, isTouched: i == touchedIndex);
case 2:
return makeGroupData(2, 5, isTouched: i == touchedIndex);
case 3:
return makeGroupData(3, 7.5, isTouched: i == touchedIndex);
case 4:
return makeGroupData(4, 9, isTouched: i == touchedIndex);
case 5:
return makeGroupData(5, 11.5, isTouched: i == touchedIndex);
case 6:
return makeGroupData(6, 6.5, isTouched: i == touchedIndex);
default:
return throw Error();
}
});
BarChartData mainBarData() {
return BarChartData(
barTouchData: BarTouchData(
touchTooltipData: BarTouchTooltipData(
tooltipBgColor: Colors.blueGrey,
getTooltipItem: (group, groupIndex, rod, rodIndex) {
String weekDay;
switch (group.x.toInt()) {
case 0:
weekDay = 'Monday';
break;
case 1:
weekDay = 'Tuesday';
break;
case 2:
weekDay = 'Wednesday';
break;
case 3:
weekDay = 'Thursday';
break;
case 4:
weekDay = 'Friday';
break;
case 5:
weekDay = 'Saturday';
break;
case 6:
weekDay = 'Sunday';
break;
default:
throw Error();
}
return BarTooltipItem(
weekDay + '\n',
const TextStyle(
color: Colors.white,
fontWeight: FontWeight.bold,
fontSize: 18,
),
children: <TextSpan>[
TextSpan(
text: (rod.toY - 1).toString(),
style: const TextStyle(
color: Colors.yellow,
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
],
);
}),
),
titlesData: FlTitlesData(
show: true,
rightTitles: SideTitles(showTitles: false),
topTitles: SideTitles(showTitles: false),
bottomTitles: SideTitles(
showTitles: true,
getTextStyles: (context, value) => const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14),
margin: 16,
getTitles: (double value) {
switch (value.toInt()) {
case 0:
return 'M';
case 1:
return 'T';
case 2:
return 'W';
case 3:
return 'T';
case 4:
return 'F';
case 5:
return 'S';
case 6:
return 'S';
default:
return '';
}
},
),
leftTitles: SideTitles(
showTitles: false,
),
),
borderData: FlBorderData(
show: false,
),
barGroups: showingGroups(),
gridData: FlGridData(show: false),
);
}
BarChartData randomData() {
return BarChartData(
barTouchData: BarTouchData(
enabled: false,
),
titlesData: FlTitlesData(
show: true,
bottomTitles: SideTitles(
showTitles: true,
getTextStyles: (context, value) => const TextStyle(
color: Colors.white, fontWeight: FontWeight.bold, fontSize: 14),
margin: 16,
getTitles: (double value) {
switch (value.toInt()) {
case 0:
return 'M';
case 1:
return 'T';
case 2:
return 'W';
case 3:
return 'T';
case 4:
return 'F';
case 5:
return 'S';
case 6:
return 'S';
default:
return '';
}
},
),
leftTitles: SideTitles(
showTitles: false,
),
topTitles: SideTitles(
showTitles: false,
),
rightTitles: SideTitles(
showTitles: false,
)),
borderData: FlBorderData(
show: false,
),
barGroups: List.generate(7, (i) {
switch (i) {
case 0:
return makeGroupData(0, Random().nextInt(15).toDouble() + 6,
barColor: Colors.blue);
case 1:
return makeGroupData(1, Random().nextInt(15).toDouble() + 6,
barColor: Colors.amber);
case 2:
return makeGroupData(2, Random().nextInt(15).toDouble() + 6,
barColor: Colors.deepOrange);
case 3:
return makeGroupData(3, Random().nextInt(15).toDouble() + 6,
barColor: Colors.pink);
case 4:
return makeGroupData(4, Random().nextInt(15).toDouble() + 6,
barColor: Colors.red);
case 5:
return makeGroupData(5, Random().nextInt(15).toDouble() + 6,
barColor: Colors.green);
case 6:
return makeGroupData(6, Random().nextInt(15).toDouble() + 6,
barColor: Colors.blueGrey);
default:
return throw Error();
}
}),
gridData: FlGridData(show: false),
);
}
}
main.dart
import 'package:flutter/material.dart';
import 'package:test_project_pdf/pdf_page.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const Scaffold(
body: Center (child: PdfPage()),
),
);
}
}
I have a 3 check box in my app, which are formed by looping over the tickbox map, in my Application it allows to select multiple checkboxes but I dont want that to happen , only one should be selected at a time, is there any way to do so in flutter.
below is my code.
class _DashboardFilterState extends State<DashboardFilter> {
void showModalSheet() {
List<Map<String, Object>> tickbox;
timeData = [
{"id": "1", "displayId": "Daily"},
{"id": "2", "displayId": "Weekly"},
{"id": "3", "displayId": "Monthly"}
];
showModalBottomSheet<void>(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
context: context,
builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter state) {
return createBox(context, timeData, state);
});
});
}
createBox(BuildContext context,List<Map<String, Object>> tickbox, StateSetter state) {
var tickboxdata = tickbox.map<Widget>((data) {
int id = data["id"];
var dispId = data["displayId"];
return buildTimeData(context, id, dispId, state);
}).toList();
return SingleChildScrollView(
child: LimitedBox(
child: Column(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
// children: metrics,
children: <Widget>[
Container(
child: Column(
children: tickboxdata,
),
),
],
)),
);
}
Widget buildTimeData(
BuildContext context, var id, var disp, StateSetter state) {
return Container(
child: Column(mainAxisSize: MainAxisSize.min,
children: <Widget>[
CheckboxListTile(
value: widget.timeSelected[id],
title: Text(disp),
controlAffinity: ListTileControlAffinity.leading,
onChanged: (bool val) {
manageTimeState(val, id, state);
})
]));
}
void manageTimeState(bool val, var id, StateSetter state) {
state(() {
widget.timeSelected[id] = val;
});
}
let me know if is there any other option to do so thanks
without any package i make the example (null safe)
Output :-
Example code :-
import 'package:flutter/material.dart';
class CheckBoxExample extends StatefulWidget {
const CheckBoxExample({Key? key}) : super(key: key);
#override
State<CheckBoxExample> createState() => _CheckBoxExampleState();
}
class _CheckBoxExampleState extends State<CheckBoxExample> {
String selected = "";
List checkListItems = [
{
"id": 0,
"value": false,
"title": "Sunday",
},
{
"id": 1,
"value": false,
"title": "Monday",
},
{
"id": 2,
"value": false,
"title": "Tuesday",
},
{
"id": 3,
"value": false,
"title": "Wednesday",
},
{
"id": 4,
"value": false,
"title": "Thursday",
},
{
"id": 5,
"value": false,
"title": "Friday",
},
{
"id": 6,
"value": false,
"title": "Saturday",
},
];
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 64.0),
child: Column(
children: [
Column(
children: List.generate(
checkListItems.length,
(index) => CheckboxListTile(
controlAffinity: ListTileControlAffinity.leading,
contentPadding: EdgeInsets.zero,
dense: true,
title: Text(
checkListItems[index]["title"],
style: const TextStyle(
fontSize: 16.0,
color: Colors.black,
),
),
value: checkListItems[index]["value"],
onChanged: (value) {
setState(() {
for (var element in checkListItems) {
element["value"] = false;
}
checkListItems[index]["value"] = value;
selected =
"${checkListItems[index]["id"]}, ${checkListItems[index]["title"]}, ${checkListItems[index]["value"]}";
});
},
),
),
),
const SizedBox(height: 100.0),
Text(
selected,
style: const TextStyle(
fontSize: 22.0,
color: Colors.black,
fontWeight: FontWeight.bold,
),
),
],
),
),
);
}
}
please use package https://pub.dev/packages/grouped_buttons
In onSelected, remove first selected item if more than one selected
code snippet
List<String> _checked = [];
...
CheckboxGroup(
labels: <String>[
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
disabled: ["Wednesday", "Friday"],
checked: _checked,
onChange: (bool isChecked, String label, int index) =>
print("isChecked: $isChecked label: $label index: $index"),
onSelected: (List selected) => setState(() {
if (selected.length > 1) {
selected.removeAt(0);
print('selected length ${selected.length}');
} else {
print("only one");
}
_checked = selected;
}),
),
full code
/*
Name: Akshath Jain
Date: 3/15/19
Purpose: example app for the grouped buttons package
*/
import 'package:flutter/material.dart';
import 'package:grouped_buttons/grouped_buttons.dart';
void main() => runApp(GroupedButtonExample());
class GroupedButtonExample extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Grouped Buttons Example',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
List<String> _checked = []; //["A", "B"];
String _picked = "Two";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Grouped Buttons Example"),
),
body: _body(),
);
//
}
Widget _body() {
return ListView(children: <Widget>[
//--------------------
//SIMPLE USAGE EXAMPLE
//--------------------
//BASIC CHECKBOXGROUP
Container(
padding: const EdgeInsets.only(left: 14.0, top: 14.0),
child: Text(
"Basic CheckboxGroup",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0),
),
),
CheckboxGroup(
labels: <String>[
"Sunday",
"Monday",
"Tuesday",
"Wednesday",
"Thursday",
"Friday",
"Saturday",
],
disabled: ["Wednesday", "Friday"],
checked: _checked,
onChange: (bool isChecked, String label, int index) =>
print("isChecked: $isChecked label: $label index: $index"),
onSelected: (List selected) => setState(() {
if (selected.length > 1) {
selected.removeAt(0);
print('selected length ${selected.length}');
} else {
print("only one");
}
_checked = selected;
}),
),
//BASIC RADIOBUTTONGROUP
Container(
padding: const EdgeInsets.only(left: 14.0, top: 14.0),
child: Text(
"Basic RadioButtonGroup",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0),
),
),
RadioButtonGroup(
labels: [
"Option 1",
"Option 2",
],
disabled: ["Option 1"],
onChange: (String label, int index) =>
print("label: $label index: $index"),
onSelected: (String label) => print(label),
),
//--------------------
//CUSTOM USAGE EXAMPLE
//--------------------
///CUSTOM CHECKBOX GROUP
Container(
padding: const EdgeInsets.only(left: 14.0, top: 14.0, bottom: 14.0),
child: Text(
"Custom CheckboxGroup",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0),
),
),
CheckboxGroup(
orientation: GroupedButtonsOrientation.HORIZONTAL,
margin: const EdgeInsets.only(left: 12.0),
onSelected: (List selected) => setState(() {
_checked = selected;
}),
labels: <String>[
"A",
"B",
],
checked: _checked,
itemBuilder: (Checkbox cb, Text txt, int i) {
return Column(
children: <Widget>[
Icon(Icons.polymer),
cb,
txt,
],
);
},
),
///CUSTOM RADIOBUTTON GROUP
Container(
padding: const EdgeInsets.only(left: 14.0, top: 14.0, bottom: 14.0),
child: Text(
"Custom RadioButtonGroup",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20.0),
),
),
RadioButtonGroup(
orientation: GroupedButtonsOrientation.HORIZONTAL,
margin: const EdgeInsets.only(left: 12.0),
onSelected: (String selected) => setState(() {
_picked = selected;
}),
labels: <String>[
"One",
"Two",
],
picked: _picked,
itemBuilder: (Radio rb, Text txt, int i) {
return Column(
children: <Widget>[
Icon(Icons.public),
rb,
txt,
],
);
},
),
]);
}
}
working demo
I have some code that should return some graphs based on a Json file.
In the example below i should return 4 graphs, instead it retruns 3 and I get an index Error.
I have no ideia of where the index error is.
import 'dart:convert';
import 'package:charts_flutter/flutter.dart' as charts;
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:http/http.dart' as http;
import 'dart:math';
class Graficos extends StatefulWidget {
#override
_GraficosState createState() => _GraficosState();
}
class _GraficosState extends State<Graficos> {
List<charts.Series> seriesList;
int qteMeses = 12;
var jsonData;
List<Widget> todosGraficos = List();
bool primeirociclo = true;
TextEditingController editingController = TextEditingController();
List<LinearSales> dados = List();
List<double> precos = List();
double menorPreco = 0;
double maiorPreco = 0;
var graficos = <Widget>[];
final myController = TextEditingController();
var staticTicks;
var duplicateItems = List<String>();
var items = List<String>();
#override
void initState() {
super.initState();
_Carregar();
}
Future<String> _Carregar() async {
primeirociclo = true;
print('local');
var response = await _funcaoJson();
if (response.length != null) {
}
return 'ok';
}
#override
Widget build(BuildContext context) {
return Container(
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
child: jsonData == null ?
Container() :
ListView(
padding: const EdgeInsets.all(10.0),
physics: AlwaysScrollableScrollPhysics(),
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height / 10,
width: MediaQuery.of(context).size.width,
child: Row(
children: <Widget>[
Expanded(
child: TextField(
onChanged: (value) {
filterSearchResults(value);
},
controller: editingController,
decoration: InputDecoration(
labelText: "Pesquisa",
hintText: "Pesquisa",
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(
borderRadius: BorderRadius.all(
Radius.circular(25.0)))),
),
)
],
),
),
Container(
height: MediaQuery.of(context).size.height / 10,
width: MediaQuery.of(context).size.width,
child: Row(
children: <Widget>[
Expanded(
child: TextFormField(
controller: myController,
keyboardType: TextInputType.number,
decoration: new InputDecoration(
hintText: 'Quantidade de meses',
),
),
),
RaisedButton(
color: Colors.lightBlue,
child: Text('Gerar Gráfico', style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 16),
),
onPressed: () async {
qteMeses = int.parse(myController.text);
await _funcaoJson();
},
),
],
),
),
ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
scrollDirection: Axis.vertical,
itemCount: jsonData['pesquisa_total'][0]['pesquisas'][0]['items'].length,
itemBuilder: (BuildContext context, int index) {
dados.clear();
precos.clear();
montaGrafico(index);
return todosGraficos[index];
},
),
]));
}
void montaGrafico(int index) {
if (jsonData['pesquisa_total'][0]['pesquisas'][0]['items'][index]['ranking'] !=null) {
for (var i = qteMeses; i >= 0; i--) {
dados.add(
LinearSales(new DateTime(int.parse(
((jsonData['pesquisa_total'][0]['pesquisas'][i]['data_publicacao'])
.toString()).split("\/")[2]), int.parse(
((jsonData['pesquisa_total'][0]['pesquisas'][i]['data_publicacao'])
.toString()).split("\/")[1]), int.parse(
((jsonData['pesquisa_total'][0]['pesquisas'][i]['data_publicacao'])
.toString()).split("\/")[0])), double.parse(
(jsonData['pesquisa_total'][0]['pesquisas'][i]['items'][index]['ranking'][0]['preco']
.toString()).replaceAll(',', '.'))),
);
precos.add(double.parse(
(jsonData['pesquisa_total'][0]['pesquisas'][i]['items'][index]['ranking'][0]['preco']
.toString()).replaceAll(',', '.')));
}
}
if (jsonData['pesquisa_total'][0]['pesquisas'][0]['items'][index]['ranking'] !=null) {
seriesList = _pegaDados(dados);
}
Widget grafico;
if (jsonData['pesquisa_total'][0]['pesquisas'][0]['items'][index]['ranking'] !=null) {
grafico = Container(
height: MediaQuery.of(context).size.height * 0.4,
width: MediaQuery.of(context).size.width * 0.7,
child: new charts.TimeSeriesChart(
seriesList,
domainAxis: new charts.DateTimeAxisSpec(
tickProviderSpec: charts.DayTickProviderSpec(increments: [30]),
),
primaryMeasureAxis: new charts.AxisSpec(
tickProviderSpec: new charts.StaticNumericTickProviderSpec(
staticTicks)),
behaviors: [
new charts.ChartTitle(
jsonData['pesquisa_total'][0]['pesquisas'][0]['items'][index]['nome_produto'].toString(),
behaviorPosition: charts.BehaviorPosition.top,
titleOutsideJustification: charts.OutsideJustification.start,
innerPadding: 18),
new charts.ChartTitle('Mes',
behaviorPosition: charts.BehaviorPosition.bottom,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.ChartTitle('Preço',
behaviorPosition: charts.BehaviorPosition.start,
titleOutsideJustification:
charts.OutsideJustification.middleDrawArea),
new charts.PanAndZoomBehavior(),
new charts.RangeAnnotation([
new charts.RangeAnnotationSegment(new DateTime(int.parse(
((jsonData['pesquisa_total'][0]['pesquisas'][qteMeses]['data_publicacao'])
.toString()).split("\/")[2]), int.parse(
((jsonData['pesquisa_total'][0]['pesquisas'][qteMeses]['data_publicacao'])
.toString()).split("\/")[1]), int.parse(
((jsonData['pesquisa_total'][0]['pesquisas'][qteMeses]['data_publicacao'])
.toString()).split("\/")[0])),
new DateTime.now(),
charts.RangeAnnotationAxisType.domain),
new charts.LineAnnotationSegment(
maiorPreco, charts.RangeAnnotationAxisType.measure,
endLabel: 'Maior Preco: $maiorPreco',
color: charts.MaterialPalette.gray.shade400),
new charts.LineAnnotationSegment(
menorPreco, charts.RangeAnnotationAxisType.measure,
startLabel: 'Menor preco: $menorPreco',
color: charts.MaterialPalette.gray.shade400),
]),
]),
);
} else {
grafico = Divider();
}
todosGraficos.add(grafico);
print(todosGraficos.length);
}
List<charts.Series<LinearSales, DateTime>> _pegaDados(List<LinearSales> data) {
menorPreco = precos.reduce(min);
maiorPreco = precos.reduce(max);
double tickPreco = (menorPreco - 0.2);
staticTicks = <charts.TickSpec<double>>[
new charts.TickSpec(
tickPreco,
label: tickPreco.toStringAsPrecision(3),
),
new charts.TickSpec(tickPreco + 0.2),
new charts.TickSpec(tickPreco + 0.4),
new charts.TickSpec(tickPreco + 0.6),
new charts.TickSpec(tickPreco + 0.8),
];
print(data);
dados = data;
return [
new charts.Series<LinearSales, DateTime>(
id: 'Preço',
displayName: 'Preço',
domainFn: (LinearSales sales, _) => sales.mes,
measureFn: (LinearSales sales, _) => sales.sales,
data: data,
)
];
}
Future<String> _funcaoJson() async {
/*var data = await http.get(
'http://itajuba.myscriptcase.com/scriptcase/devel/conf/grp/Procon/libraries/php/pesquisa_total.php?id=3&meses=$qteMeses&fornecedor_id=23');
*/
String httpJson = 'http://itajuba.myscriptcase.com/scriptcase/devel/conf/grp/Procon/libraries/php/pesquisa_total.php';
var response = await http.post(httpJson, body: {
'id': '3',
'meses': qteMeses.toString(),
'fornecedor_id': '23'
});
setState(() {
jsonData = jsonDecode(response.body);
});
return 'Sucesso';
}
void filterSearchResults(String query) {
List<String> dummySearchList = List<String>();
dummySearchList.addAll(duplicateItems);
if(query.isNotEmpty) {
List<String> dummyListData = List<String>();
dummySearchList.forEach((item) {
if(item.contains(query)) {
dummyListData.add(item);
}
});
setState(() {
items.clear();
items.addAll(dummyListData);
});
return;
} else {
setState(() {
items.clear();
items.addAll(duplicateItems);
});
}
}
}
class LinearSales {
final DateTime mes;
final double sales;
LinearSales(this.mes, this.sales);
}
I/flutter (32442): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (32442): The following RangeError was thrown building:
I/flutter (32442): RangeError (index): Invalid value: Not in range 0..6, inclusive: 8
I/flutter (32442):
I/flutter (32442): When the exception was thrown, this was the stack:
Can someone help me identify where the Index is wrong?
I foud where the problem was, my back-end guy added 2 more products this month, so when I was in itemCount: jsonData['pesquisa_total'][0]['pesquisas'][0]['items'].length it was 8, but for example itemCount: jsonData['pesquisa_total'][0]['pesquisas'][4]['items'].length it was 6 so there was my index error.