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 have this code file that loads audio playlist files to the media player direct from the internet. So, I want to change instead of loading audio playlist files direct from the internet I want it to fetch from my GraphQL API using graphql_flutter 5.1.0 (https://pub.dev/packages/graphql_flutter) package from zino.company.
My question is how can I query the list of episodes to populate the _playlist AudioSource before the page loads completely?
I tried to use didChangeDependencies but the playlist was not populated after the page loaded. Also, the Tested hard code that I commented on the file works but still it is hard coded. When the playlist is loaded, the audio files are playable and all the buttons work. I have attached the media_player.dart file and snapshot of the app.
My GraphQL API request and response for the episodes is as follow;
graphql request
{
episodeByPodcast(podcast: 1){
id
title
synopsis
duration
filePath
host
guest
podcast {
id
podcastTitle
}
recorded
posted
}
}
graphql response
{
"data": {
"episodeByPodcast": [
{
"id": "1",
"title": "Episode 1",
"synopsis": "Hello World",
"duration": "18:59",
"filePath": "podcasts/episode_1.mp3",
"host": "John Doe",
"guest": "Jane Doe",
"podcast": {
"id": "1",
"podcastTitle": "Season 1"
},
"recorded": "2022-05-27",
"posted": "2022-05-27"
},
{
"id": "4",
"title": "Episode 2",
"synopsis": "Hello World",
"duration": "18:59",
"filePath": "podcasts/episode_2.mp3",
"host": "John Doe",
"guest": "Jane Doe",
"podcast": {
"id": "1",
"podcastTitle": "Season 1"
},
"recorded": "2022-05-27",
"posted": "2022-05-27"
},
{
"id": "5",
"title": "Episode 3",
"synopsis": "Hello World",
"duration": "41:02",
"filePath": "podcasts/episode_3",
"host": "John Doe",
"guest": "Jane Doe",
"podcast": {
"id": "1",
"podcastTitle": "Season 1"
},
"recorded": "2022-05-28",
"posted": "2022-05-28"
}
]
}
}
The media_player.dart file
import 'package:audio_service/audio_service.dart';
import 'package:audio_session/audio_session.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:just_audio/just_audio.dart';
import 'package:just_audio_background/just_audio_background.dart';
import 'package:provider/provider.dart';
import 'package:rxdart/rxdart.dart';
import 'package:app/screens/podcast/widgets/common.dart';
import 'widgets/control_buttons.dart';
// Tested hard code
// const pathPrefix = MediaEndpoint.url;
// final List<Map<String, dynamic>> episodes = [
// {
// "id": "1",
// "title": "Episode 1",
// "synopsis": "Hello World",
// "duration": "18:59",
// "filePath":
// "podcasts/episode_1.mp3",
// "host": "John Doe",
// "guest": "Jane Doe",
// "podcast": {"id": "1", "podcastTitle": "Season 1"},
// "recorded": "2022-05-27",
// "posted": "2022-05-27"
// },
// {
// "id": "4",
// "title": "Episode 2",
// "synopsis": "Hello World",
// "duration": "18:59",
// "filePath":
// "podcasts/episode_2.mp3",
// "host": "John Doe",
// "guest": "Jane Doe",
// "podcast": {"id": "1", "podcastTitle": "Season 1"},
// "recorded": "2022-05-27",
// "posted": "2022-05-27"
// },
// {
// "id": "5",
// "title": "Episode 3",
// "synopsis": "Hello World",
// "duration": "41:02",
// "filePath": "podcasts/episode_3.mp3",
// "host": "John Doe",
// "guest": "Jane Doe",
// "podcast": {"id": "1", "podcastTitle": "Season 1"},
// "recorded": "2022-05-28",
// "posted": "2022-05-28"
// }
// ];
// final ConcatenatingAudioSource _playlist = ConcatenatingAudioSource(
// children: [
// for (var episode in episodes)
// AudioSource.uri(
// Uri.parse('$pathPrefix${episode['filePath']}'),
// tag: MediaItem(
// id: episode['id'].toString(),
// album: episode['podcast']['podcastTitle'].toString(),
// title: episode['title'].toString(),
// artUri: Uri.parse(
// "https://mywebsite.com/uploads/2021/04/cover-art.png"),
// ),
// ),
// ],
// );
// - End of Tested hard code -
class MediaPlayer extends StatefulWidget {
static const routeName = '/media-player';
#override
_MediaPlayerState createState() => _MediaPlayerState();
}
class _MediaPlayerState extends State<MediaPlayer> {
var _isInit = true;
var _isLoading = false;
#override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
setState(() {
_isLoading = false;
});
}
_isInit = false;
super.didChangeDependencies();
}
static int _nextMediaId = 0;
late AudioPlayer _player;
final _playlist = ConcatenatingAudioSource(
children: [
ClippingAudioSource(
start: const Duration(seconds: 60),
end: const Duration(seconds: 90),
child: AudioSource.uri(Uri.parse(
"https://mywebsite.com/podcasts/episode_1.mp3")),
tag: MediaItem(
id: '${_nextMediaId++}',
album: "Science Friday",
title: "A Salute To Head-Scratching Science (30 seconds)",
artUri: Uri.parse(
"https://mywebsite.com/uploads/2021/04/cover-art.png"),
),
),
AudioSource.uri(
Uri.parse("https://mywebsite.com/podcasts/episode_2.mp3"),
tag: MediaItem(
id: '${_nextMediaId++}',
album: "Science Friday",
title: "A Salute To Head-Scratching Science",
artUri: Uri.parse(
"https://mywebsite.com/uploads/2021/04/cover-art.png"),
),
),
AudioSource.uri(
Uri.parse(
"https://mywebsite.com/podcasts/episode_3.mp3"),
tag: MediaItem(
id: '${_nextMediaId++}',
album: "Science Friday",
title: "From Cat Rheology To Operatic Incompetence",
artUri: Uri.parse(
"https://mywebsite.com/uploads/2021/04/cover-art.png"),
),
),
AudioSource.uri(
Uri.parse(
"https://mywebsite.com/podcasts/episode_4.mp3"),
tag: MediaItem(
id: '${_nextMediaId++}',
album: "Public Domain",
title: "Nature Sounds",
artUri: Uri.parse(
"https://mywebsite.com/uploads/2021/04/cover-art.png"),
),
),
],
);
final int _addedCount = 0;
#override
void initState() {
super.initState();
_player = AudioPlayer();
SystemChrome.setSystemUIOverlayStyle(const SystemUiOverlayStyle(
statusBarColor: Colors.black,
));
_init();
}
Future<void> _init() async {
final session = await AudioSession.instance;
await session.configure(const AudioSessionConfiguration.speech());
// Listen to errors during playback.
_player.playbackEventStream.listen((event) {},
onError: (Object e, StackTrace stackTrace) {
print('A stream error occurred: $e');
});
try {
await _player.setAudioSource(_playlist);
} catch (e, stackTrace) {
// Catch load errors: 404, invalid url ...
print("Error loading playlist: $e");
print(stackTrace);
}
}
#override
void dispose() {
_player.dispose();
super.dispose();
}
Stream<PositionData> get _positionDataStream =>
Rx.combineLatest3<Duration, Duration, Duration?, PositionData>(
_player.positionStream,
_player.bufferedPositionStream,
_player.durationStream,
(position, bufferedPosition, duration) => PositionData(
position, bufferedPosition, duration ?? Duration.zero));
#override
Widget build(BuildContext context) {
final playerData = ModalRoute.of(context)!.settings.arguments as Map;
return _isLoading
? const CircularProgressIndicator()
: Scaffold(
appBar: AppBar(
title: Text(
playerData['title'],
),
),
body: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Expanded(
child: StreamBuilder<SequenceState?>(
stream: _player.sequenceStateStream,
builder: (context, snapshot) {
final state = snapshot.data;
if (state?.sequence.isEmpty ?? true) {
return const SizedBox();
}
final metadata = state!.currentSource!.tag as MediaItem;
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Image.network(
metadata.artUri.toString())),
),
),
Text(metadata.album!,
style: Theme.of(context).textTheme.headline6),
Text(metadata.title),
],
);
},
),
),
ControlButtons(_player),
StreamBuilder<PositionData>(
stream: _positionDataStream,
builder: (context, snapshot) {
final positionData = snapshot.data;
return SeekBar(
duration: positionData?.duration ?? Duration.zero,
position: positionData?.position ?? Duration.zero,
bufferedPosition:
positionData?.bufferedPosition ?? Duration.zero,
onChangeEnd: (newPosition) {
_player.seek(newPosition);
},
);
},
),
const SizedBox(height: 8.0),
Row(
children: [
StreamBuilder<LoopMode>(
stream: _player.loopModeStream,
builder: (context, snapshot) {
final loopMode = snapshot.data ?? LoopMode.off;
const icons = [
Icon(Icons.repeat, color: Colors.grey),
Icon(Icons.repeat, color: Colors.orange),
Icon(Icons.repeat_one, color: Colors.orange),
];
const cycleModes = [
LoopMode.off,
LoopMode.all,
LoopMode.one,
];
final index = cycleModes.indexOf(loopMode);
return IconButton(
icon: icons[index],
onPressed: () {
_player.setLoopMode(cycleModes[
(cycleModes.indexOf(loopMode) + 1) %
cycleModes.length]);
},
);
},
),
Expanded(
child: Text(
"Playlist",
style: Theme.of(context).textTheme.headline6,
textAlign: TextAlign.center,
),
),
StreamBuilder<bool>(
stream: _player.shuffleModeEnabledStream,
builder: (context, snapshot) {
final shuffleModeEnabled = snapshot.data ?? false;
return IconButton(
icon: shuffleModeEnabled
? const Icon(Icons.shuffle,
color: Colors.orange)
: const Icon(Icons.shuffle, color: Colors.grey),
onPressed: () async {
final enable = !shuffleModeEnabled;
if (enable) {
await _player.shuffle();
}
await _player.setShuffleModeEnabled(enable);
},
);
},
),
],
),
SizedBox(
height: 240.0,
child: StreamBuilder<SequenceState?>(
stream: _player.sequenceStateStream,
builder: (context, snapshot) {
final state = snapshot.data;
final sequence = state?.sequence ?? [];
return ReorderableListView(
onReorder: (int oldIndex, int newIndex) {
if (oldIndex < newIndex) newIndex--;
_playlist.move(oldIndex, newIndex);
},
children: [
for (var i = 0; i < sequence.length; i++)
Dismissible(
key: ValueKey(sequence[i]),
background: Container(
color: Colors.redAccent,
alignment: Alignment.centerRight,
child: const Padding(
padding: EdgeInsets.only(right: 8.0),
child:
Icon(Icons.delete, color: Colors.white),
),
),
onDismissed: (dismissDirection) {
_playlist.removeAt(i);
},
child: Material(
color: i == state!.currentIndex
? Colors.grey.shade300
: null,
child: ListTile(
title:
Text(sequence[i].tag.title as String),
onTap: () {
_player.seek(Duration.zero, index: i);
},
),
),
),
],
);
},
),
),
],
),
),
);
}
}
App screenshot
media player app screenshot
I have created a PokelistModel class and I want to display data in ServiceRequestedPage. But I am unable to access sername,pname from the model class to display in ServiceRequestedPage.
API Responses:-
{
"service": [
{
"id": "444",
"user_id": "34856",
"service_provider_id": "289",
"address": "235, AH45, Doordarshan Colony, Gajapati Nagar, Bhubaneswar, Odisha 751013, India",
"date": "2022-03-28",
"entry_time": "2022-03-28 12:16:14",
"sername": "Barber",
"pname": "Sabita Panda",
"emaildta": "",
"paddress": "Bhubaneswar, Odisha, India",
"serphone": "9853575231",
"serimage": "default.png",
"review": [],
"total_rating": 0
},
{
"id": "441",
"user_id": "34856",
"service_provider_id": "287",
"address": "235, AH45, Doordarshan Colony, Gajapati Nagar, Bhubaneswar, Odisha 751013, India",
"date": "2022-02-02",
"entry_time": "2022-02-02 16:03:11",
"sername": "Aaya & Maid ",
"pname": "Rabi Panda",
"emaildta": "",
"paddress": " Bhubaneswar, Odisha, India",
"serphone": "9853578231",
"serimage": "default.png",
"review": [
{
"id": "41",
"user_id": "34856",
"service_provider_id": "287",
"total_rating": "5.00",
"review": "g",
"entry_time": "2022-03-22 16:45:41"
},
{
"id": "42",
"user_id": "34856",
"service_provider_id": "287",
"total_rating": "5.00",
"review": "nyc",
"entry_time": "2022-03-23 16:32:33"
}
],
"total_rating": 5
},
{
"id": "431",
"user_id": "34856",
"service_provider_id": "2722",
"address": "214, Acharya Vihar, Bhubaneswar, Odisha 751013, India",
"date": "2021-02-19",
"entry_time": "2021-02-19 22:05:09",
"sername": "Hair & Spa",
"pname": "Friends Hairstyle",
"emaildta": "",
"paddress": "Plot No-88/6, Jaydev Vhir Over Bridge, Acharya Vihar, Bhubaneshwar - 751013, Near Trupati Kalyani Mandap",
"serphone": "9090531519",
"serimage": "",
"review": [
{
"id": "37",
"user_id": "34856",
"service_provider_id": "2722",
"total_rating": "5.00",
"review": "super",
"entry_time": "2021-10-20 12:11:00"
},
{
"id": "38",
"user_id": "34856",
"service_provider_id": "2722",
"total_rating": "5.00",
"review": "super",
"entry_time": "2021-10-20 12:11:02"
},
{
"id": "43",
"user_id": "34856",
"service_provider_id": "2722",
"total_rating": "5.00",
"review": "superb ",
"entry_time": "2022-03-23 16:33:00"
}
],
"total_rating": 5
}
],
"status": 1,
"message": "3 Matches Found!",
"total_review": 3
}
PokelistModel:-
import 'dart:convert';
PokelistModel pokelistModelFromJson(String str) => PokelistModel.fromJson(json.decode(str));
String pokelistModelToJson(PokelistModel data) => json.encode(data.toJson());
class PokelistModel {
PokelistModel({
required this.service,
required this.status,
required this.message,
required this.totalReview,
});
List<Service> service;
int status;
String message;
int totalReview;
factory PokelistModel.fromJson(Map<String, dynamic> json) => PokelistModel(
service: List<Service>.from(json["service"].map((x) => Service.fromJson(x))),
status: json["status"],
message: json["message"],
totalReview: json["total_review"],
);
Map<String, dynamic> toJson() => {
"service": List<dynamic>.from(service.map((x) => x.toJson())),
"status": status,
"message": message,
"total_review": totalReview,
};
}
class Service {
Service({
required this.id,
required this.userId,
required this.serviceProviderId,
required this.address,
required this.date,
required this.entryTime,
required this.sername,
required this.pname,
required this.emaildta,
required this.paddress,
required this.serphone,
required this.serimage,
required this.review,
required this.totalRating,
});
String id;
String userId;
String serviceProviderId;
String address;
DateTime date;
DateTime entryTime;
String sername;
String pname;
String emaildta;
String paddress;
String serphone;
String serimage;
List<Review> review;
int totalRating;
factory Service.fromJson(Map<String, dynamic> json) => Service(
id: json["id"],
userId: json["user_id"],
serviceProviderId: json["service_provider_id"],
address: json["address"],
date: DateTime.parse(json["date"]),
entryTime: DateTime.parse(json["entry_time"]),
sername: json["sername"],
pname: json["pname"],
emaildta: json["emaildta"],
paddress: json["paddress"],
serphone: json["serphone"],
serimage: json["serimage"],
review: List<Review>.from(json["review"].map((x) => Review.fromJson(x))),
totalRating: json["total_rating"],
);
Map<String, dynamic> toJson() => {
"id": id,
"user_id": userId,
"service_provider_id": serviceProviderId,
"address": address,
"date": "${date.year.toString().padLeft(4, '0')}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')}",
"entry_time": entryTime.toIso8601String(),
"sername": sername,
"pname": pname,
"emaildta": emaildta,
"paddress": paddress,
"serphone": serphone,
"serimage": serimage,
"review": List<dynamic>.from(review.map((x) => x.toJson())),
"total_rating": totalRating,
};
}
class Review {
Review({
required this.id,
required this.userId,
required this.serviceProviderId,
required this.totalRating,
required this.review,
required this.entryTime,
});
String id;
String userId;
String serviceProviderId;
String totalRating;
String review;
DateTime entryTime;
factory Review.fromJson(Map<String, dynamic> json) => Review(
id: json["id"],
userId: json["user_id"],
serviceProviderId: json["service_provider_id"],
totalRating: json["total_rating"],
review: json["review"],
entryTime: DateTime.parse(json["entry_time"]),
);
Map<String, dynamic> toJson() => {
"id": id,
"user_id": userId,
"service_provider_id": serviceProviderId,
"total_rating": totalRating,
"review": review,
"entry_time": entryTime.toIso8601String(),
};
}
ServiceRequestedPage :-
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_rating_bar/flutter_rating_bar.dart';
import 'package:http/http.dart';
import 'package:newbharatbiz/Model%20Class/PokelistService.dart';
import 'package:newbharatbiz/Model%20Class/pokelist_model.dart';
import 'package:newbharatbiz/Screens/AddReviewPage.dart';
import 'package:newbharatbiz/Screens/RequestedServiceDetailsPage.dart';
import 'HomePage.dart';
import 'package:http/http.dart' as http;
class ServiceRequestedPage extends StatefulWidget {
#override
_YourWidgetState createState() => _YourWidgetState();
}
class _YourWidgetState extends State<ServiceRequestedPage> {
bool isLoading = false;
late Future<PokelistModel> pokelist;
#override
void initState() {
super.initState();
pokelist = PokelistService.getProducts();
}
Future delete_poke() async {
final String DeletePokeAPI =
"https://newbharatbiz.in/mobile_api/v4/delete_poke.php";
setState(() {
isLoading = true;
});
final headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
"Content-Type": "application/x-www-form-urlencoded"
};
final Map<String, dynamic> body = {
"id": "idd",
};
String encodedBody = body.keys.map((key) => "$key=${body[key]}").join("&");
//var jsonBody = json.encode(body);
Response response = await http.post(Uri.parse(DeletePokeAPI),
body: encodedBody, headers: headers);
var jsonResponse = jsonDecode(response.body);
print(jsonResponse);
if (response.statusCode == 200) {
setState(() {
isLoading = false;
});
// If the server did return a 201 CREATED response,
// then parse the JSON.
//return json.decode(response.body)['services'];
print(response);
// return CatwiseServiceModel.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 201 CREATED response,
// then throw an exception.
throw Exception('Failed to load.');
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async => true,
child: Scaffold(
appBar: new AppBar(
title: new Text('Service Requested List'),
leading: new IconButton(
icon: new Icon(Icons.arrow_back_outlined),
onPressed: () {
Navigator.pop(
context, true); // It worked for me instead of above line
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => HomePage()),
);
}),
),
body: SingleChildScrollView(
child:
Column(mainAxisAlignment: MainAxisAlignment.start, children: [
FutureBuilder<PokelistModel>(
future: pokelist,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
//print(idd(snapshot.data[0]));
return SingleChildScrollView(
child: Column(
children: <Widget>[
ListView.builder(
padding: EdgeInsets.zero,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (BuildContext context, int index) {
return Container(
margin: EdgeInsets.zero,
child: Card(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
ListTile(
leading: CircleAvatar(
child: Image.asset(
"assets/dummyprofile.png",
fit: BoxFit.fill),
backgroundColor:
Colors.transparent,
),
title: Text(
sername(snapshot.data[index]),
style: TextStyle(
color: Colors.black87,
fontWeight:
FontWeight.bold,
fontSize: 16),
),
/*subtitle: Row(
children: <Widget>[
Text("M/S : ",
style: TextStyle(
color: Colors.black87,
fontWeight: FontWeight.bold,
fontSize: 13)),
Text(pname(snapshot.data[index]),
style: TextStyle(
color: Colors.black87)),
],
),*/
subtitle: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
"M/S : " +
pname(snapshot
.data[index]),
style: TextStyle(
color: Colors.black,
fontSize: 14)),
SizedBox(height: 5),
Text(
"Phone No : " +
serphone(snapshot
.data[index]),
style: TextStyle(
color: Colors.black,
fontSize: 14)),
],
),
trailing: new IconButton(
icon: new Icon(Icons.close,
color: Colors.black87,
size: 30.0),
onPressed: () {
delete_poke();
},
)),
SizedBox(height: 10),
Padding(
padding: const EdgeInsets.only(
left: 10.0),
child: Text(
"Address : " +
paddress(
snapshot.data[index]),
style: TextStyle(
color: Colors.black,
fontSize: 14)),
),
SizedBox(height: 15),
Padding(
padding: const EdgeInsets.only(
left: 10.0, bottom: 10.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
SizedBox(
height: 30.0,
child: MaterialButton(
child: Text("Rating"),
textColor: Colors.white,
color: Color(0xFF00796B),
disabledColor:
Colors.blue,
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius.all(
Radius.circular(
10.0), // Change your border radius here
),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
AddReviewPage(
sername: snapshot
.data[
index]
['sername'],
pname: snapshot
.data[
index]
['pname'],
),
),
);
},
),
),
SizedBox(width: 20),
SizedBox(
height: 30.0,
child: MaterialButton(
child: Text("View"),
textColor: Colors.white,
color: Color(0xFF00796B),
disabledColor:
Colors.blue,
shape:
RoundedRectangleBorder(
borderRadius:
BorderRadius.all(
Radius.circular(
10.0), // Change your border radius here
),
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
RequestedServiceDetailsPage(
sername: snapshot
.data[
index]
['sername'],
pname: snapshot
.data[
index]
['pname'],
paddress: snapshot
.data[
index][
'paddress'],
serphone: snapshot
.data[
index][
'serphone'],
),
),
);
},
),
),
// SizedBox(height: 10),
/* Button (),
RatingBar (),
Button ()*/
]),
)
])));
})
],
),
);
}
return const Center(
child: SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
),
);
}
// By default, show a loading spinner.
),
Visibility(visible: isLoading, child: CircularProgressIndicator())
]),
)),
);
}
}
PokelistService:-
import 'dart:convert';
import 'package:http/http.dart';
import 'package:newbharatbiz/Model%20Class/pokelist_model.dart';
import 'package:http/http.dart' as http;
class PokelistService {
static Future<PokelistModel> getProducts() async {
final String ServiceAPI =
"https://newbharatbiz.in/mobile_api/v4/listing_poke.php";
final headers = {
'Content-Type': 'application/json',
'Accept': 'application/json',
"Content-Type": "application/x-www-form-urlencoded"
};
final Map<String, dynamic> body = {
"user_id": "34856",
};
String encodedBody = body.keys.map((key) => "$key=${body[key]}").join("&");
//var jsonBody = json.encode(body);
Response response = await http.post(Uri.parse(ServiceAPI),
body: encodedBody, headers: headers);
var jsonResponse = jsonDecode(response.body);
print(jsonResponse);
if (response.statusCode == 200) {
// If the server did return a 201 CREATED response,
// then parse the JSON.
//return json.decode(response.body)['services'];
print(json.decode(response.body));;
final body = json.decode(response.body);
return body.map(PokelistModel.fromJson);
// var data = json.decode(response.body)
// return CatwiseServiceModel.fromJson(jsonDecode(response.body));
} else {
// If the server did not return a 201 CREATED response,
// then throw an exception.
throw Exception('Failed to load.');
}
}
}
There are a few ways how to do it. You can try this one:
Change response model to incorporate all the data. Just copy and paste JSON that you received here: https://app.quicktype.io
Then change the part here:
Text(
snapshot.data[index].service.sername,
style: TextStyle(color: Colors.black87,
fontWeight: FontWeight.bold,
fontSize: 16),
),
Use the same structure for any other data that you need. Maybe you could create more generic ApiService and pass the necessary variables as parameters, like this:
Future<ApiResponse?> post(String url, dynamic body,
{Map<String, String>? headers}) async {
print('API Post: ${this.url+url}');
try {
final response = await http.post(Uri.parse(this.url+url),
body: body,
headers: headers
);
if (response.statusCode == 200) {
String data = response.body;
final decodedData = jsonDecode(data);
print('API Post: $decodedData)');
final _apiResponse = ApiResponse.fromJson(decodedData);
return _apiResponse;
}
} catch(e) {
print('API Post: $e');
}
return null;
}
I accessed the array like this> List data = snapshot.data!.service; then will displaylike this> sername data[index].sername
List data = snapshot.data!.service;
sername data[index].sername
I have this error ("type 'String' is not a sub type of type 'int' of 'index'")
but i don't know where the problem is or which line, the compiler don't tell me which line the error is,
here is the code:
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider<Auth>(
create: (_) =>
Auth(), //lezem nekten l auth abel l prodect li2an l prodect ya3tamed 3le
),
ChangeNotifierProxyProvider<Auth, Products>(
// 2 provider product ya3tamed 3ala auth
update: (ctx, value, previousProduct) => Products(value.token,
previousProduct==null? []: previousProduct.productsList),
),
],
child: MyApp(),
),
);
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Consumer<Auth>(
builder: (ctx, value, _) => MaterialApp(
theme: ThemeData(
primaryColor: Colors.orange,
canvasColor: Color.fromRGBO(255, 238, 219, 1)),
debugShowCheckedModeBanner: false,
home: value.isAuth? MyHomePage(): AuthScreen(),
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _isLoading = true;
#override
void initState() {
Provider.of<Products>(context, listen: false)
.fetchData()
.then((_) => _isLoading = false)
.catchError((onError) => print(onError));
super.initState();
}
Widget detailCard(id, tile, desc, price, imageUrl) {
return Builder(
builder: (innerContext) => FlatButton(
onPressed: () {
print(id);
Navigator.push(
innerContext,
MaterialPageRoute(builder: (_) => ProductDetails(id)),
).then(
(id) => Provider.of<Products>(context, listen: false).delete(id));
},
child: Column(
children: [
SizedBox(height: 5),
Card(
elevation: 10,
color: Color.fromRGBO(115, 138, 119, 1),
child: Row(
children: <Widget>[
Expanded(
flex: 3,
child: Container(
padding: EdgeInsets.only(right: 10),
width: 130,
child: Hero(
tag: id,
child: Image.network(imageUrl, fit: BoxFit.fill),
),
),
),
Expanded(
flex: 3,
child: Column(
//mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
SizedBox(height: 10),
Text(
tile,
style: TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.bold),
),
Divider(color: Colors.white),
Container(
width: 200,
child: Text(
desc,
style: TextStyle(color: Colors.white, fontSize: 14),
softWrap: true,
overflow: TextOverflow.fade,
textAlign: TextAlign.justify,
maxLines: 3,
),
),
Divider(color: Colors.white),
Text(
"\$$price",
style: TextStyle(color: Colors.black, fontSize: 18),
),
SizedBox(height: 13),
],
),
),
Expanded(flex: 1, child: Icon(Icons.arrow_forward_ios)),
],
),
),
],
),
),
);
}
#override
Widget build(BuildContext context) {
List<Product> prodList =
Provider.of<Products>(context, listen: true).productsList;
return Scaffold(
appBar: AppBar(title: Text('My Products')),
body: _isLoading
? Center(child: CircularProgressIndicator())
: (prodList.isEmpty
? Center(
child: Text('No Products Added.',
style: TextStyle(fontSize: 22)))
: RefreshIndicator(
onRefresh: () async =>
await Provider.of<Products>(context, listen: false)
.fetchData(),
child: ListView(
children: prodList
.map(
(item) => detailCard(item.id, item.title,
item.description, item.price, item.imageUrl),
)
.toList(),
),
)),
floatingActionButton: Container(
width: 180,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20.0),
color: Theme.of(context).primaryColor,
),
child: FlatButton.icon(
label: Text("Add Product",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 19)),
icon: Icon(Icons.add),
onPressed: () => Navigator.push(
context, MaterialPageRoute(builder: (_) => AddProduct())),
),
),
);
}
}
product.dart
class Product {
final String id;
final String title;
final String description;
final double price;
final String imageUrl;
Product({
#required this.id,
#required this.title,
#required this.description,
#required this.price,
#required this.imageUrl,
});
}
class Products with ChangeNotifier {
List<Product> productsList = [];
final String authToken;
Products(this.authToken, this.productsList);
Future<void> fetchData() async {
final url = "https://flutter-app-568d3.firebaseio.com/product.json?auth=$authToken";
try {
final http.Response res = await http.get(url);
final extractedData = json.decode(res.body) as Map<String, dynamic>;
extractedData.forEach((prodId, prodData) {
final prodIndex =
productsList.indexWhere((element) => element.id == prodId);
if (prodIndex >= 0) {
productsList[prodIndex] = Product(
id: prodId,
title: prodData['title'],
description: prodData['description'],
price: prodData['price'],
imageUrl: prodData['imageUrl'],
);
} else {
productsList.add(Product(
id: prodId,
title: prodData['title'],
description: prodData['description'],
price: prodData['price'],
imageUrl: prodData['imageUrl'],
));
}
});
notifyListeners();
} catch (error) {
throw error;
}
}
Future<void> updateData(String id) async {
final url = "https://flutter-app-568d3.firebaseio.com/product/$id.json?auth=$authToken";
final prodIndex = productsList.indexWhere((element) => element.id == id);
if (prodIndex >= 0) {
await http.patch(url,
body: json.encode({
"title": "new title 4",
"description": "new description 2",
"price": 199.8,
"imageUrl":
"https://cdn.pixabay.com/photo/2015/06/19/21/24/the-road-815297__340.jpg",
}));
productsList[prodIndex] = Product(
id: id,
title: "new title 4",
description: "new description 2",
price: 199.8,
imageUrl:
"https://cdn.pixabay.com/photo/2015/06/19/21/24/the-road-815297__340.jpg",
);
notifyListeners();
} else {
print("...");
}
}
Future<void> add(
{String id,
String title,
String description,
double price,
String imageUrl}) async {
final url = "https://flutter-app-568d3.firebaseio.com/product.json?auth=$authToken";
try {
http.Response res = await http.post(url,
body: json.encode({
"title": title,
"description": description,
"price": price,
"imageUrl": imageUrl,
}));
print(json.decode(res.body));
productsList.add(Product(
id: json.decode(res.body)['name'],
title: title,
description: description,
price: price,
imageUrl: imageUrl,
));
notifyListeners();
} catch (error) {
throw error;
}
}
Future<void> delete(String id) async {
final url = "https://flutter-app-568d3.firebaseio.com/product/$id.json?auth=$authToken";
final prodIndex = productsList.indexWhere((element) => element.id == id);
var prodItem = productsList[prodIndex];
productsList.removeAt(prodIndex);
notifyListeners();
var res = await http.delete(url);
if (res.statusCode >= 400) {
productsList.insert(prodIndex, prodItem);
notifyListeners();
print("Could not deleted item");
} else {
prodItem = null;
print("Item deleted");
}
}
}
auth.dart
class Auth with ChangeNotifier {
String _token;
DateTime _expireDate;
String _userId;
bool get isAuth{
return token != null;
}
String get token{
if(_expireDate != null && _expireDate.isAfter(DateTime.now()) && _token != null){
return _token;
}return null;
}
Future<void> _authenticate(
String email, String password, String urlSegment) async {
final url =
"https://identitytoolkit.googleapis.com/v1/accounts:$urlSegment?key=AIzaSyAN1Z9ibJ9-HqBen65x5ALKiqsRTzFfhh8";
try {
final res = await http.post(url,
body: json.encode({
'email': email,
'password': password,
'returnSecureToken': true,
}));
print(json.decode(res.body));// byetba3 lma3lomat l rej3a ya3ni resalet l error
final resData = json.decode(res.body);
// to print the error
if(resData['error'] != null){
throw "${resData['error']['message']}";
}
_token = resData['idToken'];
_userId = resData['localId'];
_expireDate = DateTime.now().add(Duration(seconds: int.parse(resData['expiresIn'])));
notifyListeners();
} catch (e) {
throw e;
}
}
Future<void> signUp(String email, String password) async {
return _authenticate(email, password, "signUp");
}
Future<void> login(String email, String password) async {
return _authenticate(email, password, "signInWithPassword");
}
}
auth_screen.dart
enum AuthMode { SignUp, Login }
class AuthScreen extends StatelessWidget {
static const routeName = '/auth';
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return Scaffold(
// resizeToAvoidBottomInset: false,
body: Stack(
children: <Widget>[
Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
Color.fromRGBO(215, 117, 255, 1).withOpacity(0.5),
Color.fromRGBO(255, 188, 117, 1).withOpacity(0.9),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
stops: [0, 1],
),
),
),
SingleChildScrollView(
child: Container(
height: deviceSize.height,
width: deviceSize.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Flexible(
child: Container(
margin: EdgeInsets.only(bottom: 20.0),
padding:
EdgeInsets.symmetric(vertical: 8.0, horizontal: 94.0),
transform: Matrix4.rotationZ(-8 * pi / 180)
..translate(-10.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
color: Colors.deepOrange.shade900,
boxShadow: [
BoxShadow(
blurRadius: 8,
color: Colors.black26,
offset: Offset(0, 2),
)
],
),
child: Text(
'MyShop',
style: TextStyle(
color: Colors.white,
fontSize: 40,
),
),
),
),
Flexible(
flex: deviceSize.width > 600 ? 2 : 1,
child: AuthCard(),
),
],
),
),
),
],
),
);
}
}
class AuthCard extends StatefulWidget {
const AuthCard({
Key key,
}) : super(key: key);
#override
_AuthCardState createState() => _AuthCardState();
}
class _AuthCardState extends State<AuthCard> {
final GlobalKey<FormState> _formKey = GlobalKey();
AuthMode _authMode = AuthMode.Login;
Map<String, String> _authData = {
'email': '',
'password': '',
};
var _isLoading = false;
final _passwordController = TextEditingController();
void _showErrorDialog(String message) {
showDialog(
context: context,
builder: (ctx) => AlertDialog(
title: Text("An Error Occurred"),
content: Text(message),
actions: <Widget>[
FlatButton(
onPressed: () {
Navigator.of(ctx).pop();
},
child: Text("OK")),
],
));
}
Future<void> _submit() async{
if (!_formKey.currentState.validate()) {
// Invalid!
return;
}
_formKey.currentState.save();
setState(() {
_isLoading = true;
});
try {
if (_authMode == AuthMode.Login) {
// Log user in
await Provider.of<Auth>(context, listen: false)
.login(
_authData['email'],
_authData['password'],
);
} else {
// Sign user up
await Provider.of<Auth>(context, listen: false)
.signUp(
_authData['email'],
_authData['password'],
);
}
} catch (error) {
var errorMessage = "Authentication failed";
if(error.toString().contains('EMAIL_EXISTS')){
errorMessage = 'This email address is already taken';
}else if(error.toString().contains('INVALID_EMAIL')){
errorMessage = 'This is not a valid email address';
}else if(error.toString().contains('WEAK_PASSWORD')){
errorMessage = 'This password is too weak';
} else if(error.toString().contains('EMAIL_NOT_FOUND')){
errorMessage = 'Could not find a user with that email';
} else if(error.toString().contains('INVALID_PASSWORD')){
errorMessage = 'Invalid password';
}
_showErrorDialog(errorMessage);
}
setState(() {
_isLoading = false;
});
}
void _switchAuthMode() {
if (_authMode == AuthMode.Login) {
setState(() {
_authMode = AuthMode.SignUp;
});
} else {
setState(() {
_authMode = AuthMode.Login;
});
}
}
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
return Card(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10.0),
),
elevation: 8.0,
child: Container(
height: _authMode == AuthMode.SignUp ? 320 : 260,
constraints:
BoxConstraints(minHeight: _authMode == AuthMode.SignUp ? 320 : 260),
width: deviceSize.width * 0.75,
padding: EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: SingleChildScrollView(
child: Column(
children: <Widget>[
TextFormField(
decoration: InputDecoration(labelText: 'E-Mail'),
keyboardType: TextInputType.emailAddress,
validator: (value) {
if (value.isEmpty || !value.contains('#')) {
return 'Invalid email!';
}
return null;
},
onSaved: (value) {
_authData['email'] = value;
},
),
TextFormField(
decoration: InputDecoration(labelText: 'Password'),
obscureText: true,
controller: _passwordController,
validator: (value) {
if (value.isEmpty || value.length < 5) {
return 'Password is too short!';
}
return null;
},
onSaved: (value) {
_authData['password'] = value;
},
),
if (_authMode == AuthMode.SignUp)
TextFormField(
enabled: _authMode == AuthMode.SignUp,
decoration: InputDecoration(labelText: 'Confirm Password'),
obscureText: true,
validator: _authMode == AuthMode.SignUp
? (value) {
if (value != _passwordController.text) {
return 'Passwords do not match!';
}
return null;
}
: null,
),
SizedBox(
height: 20,
),
if (_isLoading)
CircularProgressIndicator()
else
RaisedButton(
child:
Text(_authMode == AuthMode.Login ? 'LOGIN' : 'SIGN UP'),
onPressed: _submit,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
padding:
EdgeInsets.symmetric(horizontal: 30.0, vertical: 8.0),
color: Theme.of(context).primaryColor,
textColor: Theme.of(context).primaryTextTheme.button.color,
),
FlatButton(
child: Text(
'${_authMode == AuthMode.Login ? 'SIGN UP' : 'LOGIN'} INSTEAD'),
onPressed: _switchAuthMode,
padding: EdgeInsets.symmetric(horizontal: 30.0, vertical: 4),
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
textColor: Theme.of(context).primaryColor,
),
],
),
),
),
),
);
}
}
Compiler
Launching lib\main.dart on SM G610F in debug mode...
Running Gradle task 'assembleDebug'...
√ Built build\app\outputs\flutter-apk\app-debug.apk.
Installing build\app\outputs\flutter-apk\app.apk...
Waiting for SM G610F to report its views...
Debug service listening on ws://127.0.0.1:60889/J5CQX-fKfbs=/ws
Syncing files to device SM G610F...
I/zygote (32524): Do partial code cache collection, code=30KB, data=25KB
I/zygote (32524): After code cache collection, code=30KB, data=25KB
I/zygote (32524): Increasing code cache capacity to 128KB
D/libGLESv2(32524): STS_GLApi : DTS, ODTC are not allowed for Package : com.example.animation
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 0
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 1
D/InputMethodManager(32524): SSI - flag : 0 Pid : 32524 view : com.example.animation
V/InputMethodManager(32524): Starting input: tba=android.view.inputmethod.EditorInfo#9dca947 nm : com.example.animation ic=io.flutter.plugin.editing.InputConnectionAdaptor#271e574
D/InputMethodManager(32524): startInputInner - Id : 0
I/InputMethodManager(32524): startInputInner - mService.startInputOrWindowGainedFocus
D/InputTransport(32524): Input channel constructed: fd=100
D/InputTransport(32524): Input channel destroyed: fd=91
D/ViewRootImpl#886418b[MainActivity](32524): MSG_RESIZED: frame=Rect(0, 0 - 1080, 1920) ci=Rect(0, 72 - 0, 873) vi=Rect(0, 72 - 0, 873) or=1
D/ViewRootImpl#886418b[MainActivity](32524): Relayout returned: old=[0,0][1080,1920] new=[0,0][1080,1920] result=0x1 surface={valid=true 3430410240} changed=false
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 0
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 1
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 0
I/zygote (32524): Do partial code cache collection, code=61KB, data=61KB
I/zygote (32524): After code cache collection, code=61KB, data=61KB
I/zygote (32524): Increasing code cache capacity to 256KB
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 1
V/InputMethodManager(32524): Starting input: tba=android.view.inputmethod.EditorInfo#fb092e3 nm : com.example.animation ic=io.flutter.plugin.editing.InputConnectionAdaptor#981e4e0
D/InputMethodManager(32524): startInputInner - Id : 0
I/InputMethodManager(32524): startInputInner - mService.startInputOrWindowGainedFocus
D/InputTransport(32524): Input channel constructed: fd=94
D/InputTransport(32524): Input channel destroyed: fd=100
W/IInputConnectionWrapper(32524): getExtractedText on inactive InputConnection
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 0
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 1
D/InputMethodManager(32524): SSI - flag : 0 Pid : 32524 view : com.example.animation
V/InputMethodManager(32524): Starting input: tba=android.view.inputmethod.EditorInfo#391b65e nm : com.example.animation ic=io.flutter.plugin.editing.InputConnectionAdaptor#54f7a3f
D/InputMethodManager(32524): startInputInner - Id : 0
I/InputMethodManager(32524): startInputInner - mService.startInputOrWindowGainedFocus
D/InputTransport(32524): Input channel constructed: fd=97
D/InputTransport(32524): Input channel destroyed: fd=94
D/InputMethodManager(32524): HSIFW - flag : 0 Pid : 32524
D/ViewRootImpl#886418b[MainActivity](32524): MSG_RESIZED: frame=Rect(0, 0 - 1080, 1920) ci=Rect(0, 72 - 0, 0) vi=Rect(0, 72 - 0, 0) or=1
D/ViewRootImpl#886418b[MainActivity](32524): Relayout returned: old=[0,0][1080,1920] new=[0,0][1080,1920] result=0x1 surface={valid=true 3430410240} changed=false
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 0
D/ViewRootImpl#886418b[MainActivity](32524): ViewPostIme pointer 1
I/flutter (32524): {kind: identitytoolkit#VerifyPasswordResponse, localId: urJPn2l2EBeHNGJ7MmcMMkCIJtf1, email: ali#gmail.com, displayName: , idToken: eyJhbGciOiJSUzI1NiIsImtpZCI6ImM0ZWFhZjkxM2VlNWY0MDY0YmE2NjUzN2M0Njk3YzY5OGE3NGYwODIiLCJ0eXAiOiJKV1QifQ.eyJpc3MiOiJodHRwczovL3NlY3VyZXRva2VuLmdvb2dsZS5jb20vZmx1dHRlci1hcHAtYjM0ZTAiLCJhdWQiOiJmbHV0dGVyLWFwcC1iMzRlMCIsImF1dGhfdGltZSI6MTYxNDg4MzQwMywidXNlcl9pZCI6InVySlBuMmwyRUJlSE5HSjdNbWNNTWtDSUp0ZjEiLCJzdWIiOiJ1ckpQbjJsMkVCZUhOR0o3TW1jTU1rQ0lKdGYxIiwiaWF0IjoxNjE0ODgzNDAzLCJleHAiOjE2MTQ4ODcwMDMsImVtYWlsIjoiYWxpQGdtYWlsLmNvbSIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwiZmlyZWJhc2UiOnsiaWRlbnRpdGllcyI6eyJlbWFpbCI6WyJhbGlAZ21haWwuY29tIl19LCJzaWduX2luX3Byb3ZpZGVyIjoicGFzc3dvcmQifX0.XfeiI4-UqDcaugW1mQxaoQbrk-h_NclPANgKKt0w36H6Jj32mGdUV-pZ_WdXdfMRxnF9lJEt9nfe0FgQSVKyYk8oLxkEaqlNJME1BnF5wUvHEJsqpHsG89mFz77cYWw-jYcjvquvPlJYSiwIc-snEyinRPx5DZwpbIkWtGe-oqWzJyE23C5inh0yu0O52tLrEDtBHy5m9qBvCK-3HLHKJ1Phrz0Q06qdH8cxw_3-AdRPqZQ8w8F4FhVOyrB04NEqWIBs-uvjAkbjo6y3LlM5PqLebrhDSdKlUlbVYOVPotMp0UMkcDRqkuZDlFZoMRoU
D/ViewRootImpl#886418b[MainActivity](32524): Relayout returned: old=[0,0][1080,1920] new=[0,0][1080,1920] result=0x1 surface={valid=true 3430410240} changed=false
I/flutter (32524): type 'String' is not a subtype of type 'int' of 'index'
D/ViewRootImpl#886418b[MainActivity](32524): MSG_WINDOW_FOCUS_CHANGED 0
D/ViewRootImpl#886418b[MainActivity](32524): MSG_WINDOW_FOCUS_CHANGED 1
I need a help please:
what i need to know is where is the error and how to solve it.
thank you in advance
I'm populating states name from JSON and display as a dropdown list after that I show 2nd dropdown list based on first value selected. But I don't now how to populate 2nd dropdown when 1st dropdown value changed. I also want to get location id, name and city when select 2nd dropdown value.
My Json
{
"data": [
{
"state": "TEXAS",
"locations": [
{
"id": 1,
"name": "FITT Sugarland",
"city": "HOUSTON",
"state": "TEXAS",
"timezone": "",
"ownerName": "",
"ownerPhoneNumber": "",
"ownerEmail": "",
"ownerWebsite": "",
"capacity": 0,
"description": "HOUSTON SUGARLAND",
"createdBy": "",
"modifiedBy": "",
"createdAt": "2020-08-18T10:17:55.000Z",
"modifiedAt": "2020-08-18T10:17:55.000Z"
},
{
"id": 2,
"name": "FITT Pearland",
"city": "HOUSTON",
"state": "TEXAS",
"timezone": "",
"ownerName": "",
"ownerPhoneNumber": "",
"ownerEmail": "",
"ownerWebsite": "",
"capacity": 0,
"description": "second location",
"createdBy": "",
"modifiedBy": "",
"createdAt": "2020-08-18T10:18:38.000Z",
"modifiedAt": "2020-08-18T10:18:38.000Z"
}
]
}
]
}
Model Class
class LocationModel {
List<LocationList> data;
LocationModel({this.data});
LocationModel.fromJson(Map<String, dynamic> json) {
if (json['data'] != null) {
data = new List<LocationList>();
json['data'].forEach((v) {
data.add(new LocationList.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
if (this.data != null) {
data['data'] = this.data.map((v) => v.toJson()).toList();
}
return data;
}
}
class LocationList {
String state;
List<Locations> locations;
LocationList({this.state, this.locations});
LocationList.fromJson(Map<String, dynamic> json) {
state = json['state'];
if (json['locations'] != null) {
locations = new List<Locations>();
json['locations'].forEach((v) {
locations.add(new Locations.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['state'] = this.state;
if (this.locations != null) {
data['locations'] = this.locations.map((v) => v.toJson()).toList();
}
return data;
}
}
class Locations {
int id;
String name;
String city;
String state;
String timezone;
String ownerName;
String ownerPhoneNumber;
String ownerEmail;
String ownerWebsite;
int capacity;
String description;
String createdBy;
String modifiedBy;
String createdAt;
String modifiedAt;
Locations(
{this.id,
this.name,
this.city,
this.state,
this.timezone,
this.ownerName,
this.ownerPhoneNumber,
this.ownerEmail,
this.ownerWebsite,
this.capacity,
this.description,
this.createdBy,
this.modifiedBy,
this.createdAt,
this.modifiedAt});
Locations.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
city = json['city'];
state = json['state'];
timezone = json['timezone'];
ownerName = json['ownerName'];
ownerPhoneNumber = json['ownerPhoneNumber'];
ownerEmail = json['ownerEmail'];
ownerWebsite = json['ownerWebsite'];
capacity = json['capacity'];
description = json['description'];
createdBy = json['createdBy'];
modifiedBy = json['modifiedBy'];
createdAt = json['createdAt'];
modifiedAt = json['modifiedAt'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['city'] = this.city;
data['state'] = this.state;
data['timezone'] = this.timezone;
data['ownerName'] = this.ownerName;
data['ownerPhoneNumber'] = this.ownerPhoneNumber;
data['ownerEmail'] = this.ownerEmail;
data['ownerWebsite'] = this.ownerWebsite;
data['capacity'] = this.capacity;
data['description'] = this.description;
data['createdBy'] = this.createdBy;
data['modifiedBy'] = this.modifiedBy;
data['createdAt'] = this.createdAt;
data['modifiedAt'] = this.modifiedAt;
return data;
}
}
My Code
import 'dart:io';
import 'package:fittheorem/models/location_list.dart';
import 'package:fittheorem/providers/auth_session.dart';
import 'package:fittheorem/providers/user_details.dart';
import 'package:fittheorem/ui/widgets/error_snakbar.dart';
import 'package:fittheorem/utils/app_config.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart';
class LocationUpdate extends StatefulWidget {
#override
_LocationUpdateState createState() => _LocationUpdateState();
}
class _LocationUpdateState extends State<LocationUpdate> {
LocationModel _locationModel;
bool isLoading = true;
String _selectedState = "TEXAS";
List<String> _statesList = [];
String _selectedLocation = "FITT Sugarland";
List<Locations> _locationsList = List();
#override
void initState() {
// TODO: implement initState
super.initState();
getList();
}
Future<void> getList() async {
try {
_locationModel = await Provider.of<UserDetails>(context, listen: false)
.getLocationList("token");
for (int i = 0; i < _locationModel.data.length; i++) {
_statesList.add(_locationModel.data[i].state);
}
_locationsList = _locationModel.data[0].locations;
_selectedState = _statesList[0];
_selectedLocation = _locationsList[0].name;
if (mounted) {
setState(() {
isLoading = false;
});
}
} on HttpException catch (error) {
CustomWidgets.buildErrorSnackbar(context);
} catch (error) {
CustomWidgets.buildErrorSnackbar(context);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
color: AppConfig.bgColor,
child: SafeArea(
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
height: 40.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Navigator.pop(context, true);
},
child: Container(
height: 25.0,
width: 25.0,
child: SvgPicture.asset(AppConfig.backImage,
color: Colors.white, semanticsLabel: 'back')),
),
Text('LOCATION',
style: GoogleFonts.roboto(
textStyle: TextStyle(
fontSize: 18.0, color: Color(0xffFFFFFF)),
)),
SizedBox(
width: 25.0,
),
],
),
),
Expanded(
child: Container(
color: Colors.white,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(30.0),
child: isLoading
? Center(
child: AppConfig().myLoader(),
)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsets.only(left: 5.0),
child: Text('UPDATE YOUR LOCATION',
style: GoogleFonts.roboto(
textStyle: TextStyle(
fontSize: 16.0,
color: Color(0xff000000)),
)),
),
SizedBox(
height: 20.0,
),
Container(
height: 40.0,
decoration: new BoxDecoration(
border: new Border.all(
color: Colors.black54, width: 0.0),
borderRadius: new BorderRadius.circular(10.0),
),
child: new DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: new DropdownButton(
value: _selectedState,
hint: Text("State"),
isExpanded: true,
items: _statesList
.map((String item) =>
DropdownMenuItem<String>(
child: Text(item),
value: item))
.toList(),
onChanged: (String newValue) {
if (mounted)
setState(() {
_selectedState = newValue;
});
},
style: Theme.of(context)
.textTheme
.bodyText2,
))),
),
SizedBox(
height: 20.0,
),
Container(
height: 40.0,
decoration: new BoxDecoration(
border: new Border.all(
color: Colors.black54, width: 0.0),
borderRadius: new BorderRadius.circular(10.0),
),
child: new DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: new DropdownButton(
value: _selectedLocation,
hint: Text("Location"),
isExpanded: true,
items: _locationsList.map((item) {
return new DropdownMenuItem(
child: new Text(item.name),
value: item.name,
);
}).toList(),
onChanged: (newValue) {
if (mounted) print(newValue);
setState(() {
_selectedLocation = newValue;
});
},
style: Theme.of(context)
.textTheme
.bodyText2,
))),
)
],
),
),
)
],
),
),
),
);
}
}
You can copy paste run full code below
Step 1: Change data type of _selectedLocation
Locations _selectedLocation;
Step 2: _selectedState's onChanged
onChanged: (String Value) {
if (mounted)
setState(() {
_selectedState = Value;
int index = _locationModel.data
.indexWhere((element) =>
element.state ==
_selectedState);
_locationsList = _locationModel
.data[index].locations;
_selectedLocation =
_locationsList[0];
});
},
Step 3: _selectedLocation's onChanged
onChanged: (Value) {
if (mounted) print(Value);
setState(() {
_selectedLocation = Value;
print(
"${_selectedLocation.name} ${_selectedLocation.id} ${_selectedLocation.city}");
});
},
working demo
full code
import 'dart:convert';
import 'package:flutter/material.dart';
LocationModel locationModelFromJson(String str) =>
LocationModel.fromJson(json.decode(str));
class LocationModel {
List<LocationList> data;
LocationModel({this.data});
LocationModel.fromJson(Map<String, dynamic> json) {
if (json['data'] != null) {
data = List<LocationList>();
json['data'].forEach((v) {
data.add(LocationList.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = Map<String, dynamic>();
if (this.data != null) {
data['data'] = this.data.map((v) => v.toJson()).toList();
}
return data;
}
}
class LocationList {
String state;
List<Locations> locations;
LocationList({this.state, this.locations});
LocationList.fromJson(Map<String, dynamic> json) {
state = json['state'];
if (json['locations'] != null) {
locations = List<Locations>();
json['locations'].forEach((v) {
locations.add(Locations.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = Map<String, dynamic>();
data['state'] = this.state;
if (this.locations != null) {
data['locations'] = this.locations.map((v) => v.toJson()).toList();
}
return data;
}
}
class Locations {
int id;
String name;
String city;
String state;
String timezone;
String ownerName;
String ownerPhoneNumber;
String ownerEmail;
String ownerWebsite;
int capacity;
String description;
String createdBy;
String modifiedBy;
String createdAt;
String modifiedAt;
Locations(
{this.id,
this.name,
this.city,
this.state,
this.timezone,
this.ownerName,
this.ownerPhoneNumber,
this.ownerEmail,
this.ownerWebsite,
this.capacity,
this.description,
this.createdBy,
this.modifiedBy,
this.createdAt,
this.modifiedAt});
Locations.fromJson(Map<String, dynamic> json) {
id = json['id'];
name = json['name'];
city = json['city'];
state = json['state'];
timezone = json['timezone'];
ownerName = json['ownerName'];
ownerPhoneNumber = json['ownerPhoneNumber'];
ownerEmail = json['ownerEmail'];
ownerWebsite = json['ownerWebsite'];
capacity = json['capacity'];
description = json['description'];
createdBy = json['createdBy'];
modifiedBy = json['modifiedBy'];
createdAt = json['createdAt'];
modifiedAt = json['modifiedAt'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['city'] = this.city;
data['state'] = this.state;
data['timezone'] = this.timezone;
data['ownerName'] = this.ownerName;
data['ownerPhoneNumber'] = this.ownerPhoneNumber;
data['ownerEmail'] = this.ownerEmail;
data['ownerWebsite'] = this.ownerWebsite;
data['capacity'] = this.capacity;
data['description'] = this.description;
data['createdBy'] = this.createdBy;
data['modifiedBy'] = this.modifiedBy;
data['createdAt'] = this.createdAt;
data['modifiedAt'] = this.modifiedAt;
return data;
}
}
class LocationUpdate extends StatefulWidget {
#override
_LocationUpdateState createState() => _LocationUpdateState();
}
class _LocationUpdateState extends State<LocationUpdate> {
LocationModel _locationModel;
bool isLoading = true;
String _selectedState = "TEXAS";
List<String> _statesList = [];
Locations _selectedLocation;
List<Locations> _locationsList = List();
#override
void initState() {
// TODO: implement initState
super.initState();
getList();
}
Future<void> getList() async {
try {
/*_locationModel = await Provider.of<UserDetails>(context, listen: false)
.getLocationList("token");*/
String jsonString = '''
{
"data": [
{
"state": "TEXAS",
"locations": [
{
"id": 1,
"name": "FITT Sugarland",
"city": "HOUSTON",
"state": "TEXAS",
"timezone": "",
"ownerName": "",
"ownerPhoneNumber": "",
"ownerEmail": "",
"ownerWebsite": "",
"capacity": 0,
"description": "HOUSTON SUGARLAND",
"createdBy": "",
"modifiedBy": "",
"createdAt": "2020-08-18T10:17:55.000Z",
"modifiedAt": "2020-08-18T10:17:55.000Z"
},
{
"id": 2,
"name": "FITT Pearland",
"city": "HOUSTON",
"state": "TEXAS",
"timezone": "",
"ownerName": "",
"ownerPhoneNumber": "",
"ownerEmail": "",
"ownerWebsite": "",
"capacity": 0,
"description": "second location",
"createdBy": "",
"modifiedBy": "",
"createdAt": "2020-08-18T10:18:38.000Z",
"modifiedAt": "2020-08-18T10:18:38.000Z"
}
]
},
{
"state": "A",
"locations": [
{
"id": 1,
"name": "A1",
"city": "A City 1",
"state": "A",
"timezone": "",
"ownerName": "",
"ownerPhoneNumber": "",
"ownerEmail": "",
"ownerWebsite": "",
"capacity": 0,
"description": "HOUSTON SUGARLAND",
"createdBy": "",
"modifiedBy": "",
"createdAt": "2020-08-18T10:17:55.000Z",
"modifiedAt": "2020-08-18T10:17:55.000Z"
},
{
"id": 2,
"name": "A2",
"city": "A city 2",
"state": "A",
"timezone": "",
"ownerName": "",
"ownerPhoneNumber": "",
"ownerEmail": "",
"ownerWebsite": "",
"capacity": 0,
"description": "second location",
"createdBy": "",
"modifiedBy": "",
"createdAt": "2020-08-18T10:18:38.000Z",
"modifiedAt": "2020-08-18T10:18:38.000Z"
}
]
}
]
}
''';
_locationModel = locationModelFromJson(jsonString);
for (int i = 0; i < _locationModel.data.length; i++) {
_statesList.add(_locationModel.data[i].state);
}
_locationsList = _locationModel.data[0].locations;
_selectedState = _statesList[0];
_selectedLocation = _locationsList[0];
if (mounted) {
setState(() {
isLoading = false;
});
}
//} on HttpException catch (error) {
// CustomWidgets.buildErrorSnackbar(context);
} catch (error) {
//CustomWidgets.buildErrorSnackbar(context);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
//color: AppConfig.bgColor,
child: SafeArea(
child: Column(
children: <Widget>[
Container(
padding: EdgeInsets.fromLTRB(20.0, 10.0, 20.0, 10.0),
height: 40.0,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
GestureDetector(
behavior: HitTestBehavior.translucent,
onTap: () {
Navigator.pop(context, true);
},
child: Container(
height: 25.0,
width: 25.0,
child: Image.network(
'https://picsum.photos/250?image=9',
color: Colors.white)),
),
Text('LOCATION',
style: TextStyle(
fontSize: 18.0,
color: Color(0xffFFFFFF),
)),
SizedBox(
width: 25.0,
),
],
),
),
Expanded(
child: Container(
color: Colors.white,
height: MediaQuery.of(context).size.height,
width: MediaQuery.of(context).size.width,
padding: EdgeInsets.all(30.0),
child: isLoading
? Center(
child: CircularProgressIndicator(),
)
: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
height: 20.0,
),
Padding(
padding: const EdgeInsets.only(left: 5.0),
child: Text('UPDATE YOUR LOCATION',
style: TextStyle(
fontSize: 16.0,
color: Color(0xff000000),
)),
),
SizedBox(
height: 20.0,
),
Container(
height: 40.0,
decoration: BoxDecoration(
border: Border.all(
color: Colors.black54, width: 0.0),
borderRadius: BorderRadius.circular(10.0),
),
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton(
value: _selectedState,
hint: Text("State"),
isExpanded: true,
items: _statesList
.map((String item) =>
DropdownMenuItem<String>(
child: Text(item),
value: item))
.toList(),
onChanged: (String Value) {
if (mounted)
setState(() {
_selectedState = Value;
int index = _locationModel.data
.indexWhere((element) =>
element.state ==
_selectedState);
_locationsList = _locationModel
.data[index].locations;
_selectedLocation =
_locationsList[0];
});
},
style: Theme.of(context)
.textTheme
.bodyText2,
))),
),
SizedBox(
height: 20.0,
),
Container(
height: 40.0,
decoration: BoxDecoration(
border: Border.all(
color: Colors.black54, width: 0.0),
borderRadius: BorderRadius.circular(10.0),
),
child: DropdownButtonHideUnderline(
child: ButtonTheme(
alignedDropdown: true,
child: DropdownButton(
value: _selectedLocation,
hint: Text("Location"),
isExpanded: true,
items: _locationsList.map((item) {
return DropdownMenuItem(
child: Text(item.name),
value: item,
);
}).toList(),
onChanged: (Value) {
if (mounted) print(Value);
setState(() {
_selectedLocation = Value;
print(
"${_selectedLocation.name} ${_selectedLocation.id} ${_selectedLocation.city}");
});
},
style: Theme.of(context)
.textTheme
.bodyText2,
))),
)
],
),
),
)
],
),
),
),
);
}
}
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: LocationUpdate(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
'You have pushed the button this many times:',
),
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: _incrementCounter,
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}