Related
I am working on a large-scale project and integrating video calls into it. So I made a separate project just for practice and I achieved good results. The group calling worked perfectly on both android and IOS. But then I integrated the same code in my large-scale project which uses firebase as a backend and when I navigate to the video screen it gives me an error saying "Unhandled Exception: LateInitializationError: Field 'requestPort' has not been initialized". The Agora console is on Testing for now and the channel wasn't expired just in case you guys are wondering. As I said it works perfectly in a separate project.
import 'package:agora_rtc_engine/agora_rtc_engine.dart';
import 'package:flutter/material.dart';
import 'package:logger/logger.dart';
class VideoCallPage extends StatefulWidget {
const VideoCallPage({Key? key}) : super(key: key);
#override
VideoCallPageState createState() => VideoCallPageState();
}
class VideoCallPageState extends State<VideoCallPage> {
static final _users = <int>[];
Logger logger = Logger();
bool muted = false;
late RtcEngine _engine;
bool isRigning = true;
bool isSpeakerOn = true;
final String channelName = 'video';
final String appID = 'xxx';
final String tokenAudio ='xxx';
#override
void dispose() {
_dispose();
super.dispose();
}
Future<void> _dispose() async {
_users.clear();
await _engine.leaveChannel();
await _engine.stopPreview();
await _engine.release();
}
#override
void initState() {
super.initState();
// initialize agora sdk
initialize();
}
Future<void> initialize() async {
logger.i('Initialize');
if (appID.isEmpty) {
setState(() {
logger.e(
'APP_ID missing, please provide your APP_ID in settings.dart',
);
logger.e('Agora Engine is not starting');
});
return;
}
await _initAgoraRtcEngine();
_addAgoraEventHandlers();
onOffSpeaker();
await _engine.joinChannel(
token: tokenAudio,
channelId: channelName,
uid: 0,
options: const ChannelMediaOptions(
channelProfile: ChannelProfileType.channelProfileCommunication,
clientRoleType: ClientRoleType.clientRoleBroadcaster));
}
Future<void> _initAgoraRtcEngine() async {
logger.i('_initAgoraRtcEngine');
//create the engine
_engine = createAgoraRtcEngine();
logger.i('RtcEngineContext');
await _engine.initialize(RtcEngineContext(
appId: appID,
));
logger.i('enablbing video');
await _engine.enableVideo();
// await _engine.setVideoEncoderConfiguration(
// const VideoEncoderConfiguration(
// dimensions: VideoDimensions(width: 640, height: 360),
// frameRate: 15,
// bitrate: 0,
// ),
// );
await _engine.startPreview();
}
void _addAgoraEventHandlers() {
_engine.registerEventHandler(RtcEngineEventHandler(
onError: (ErrorCodeType errorCodeType, String value) {
if (mounted) {
setState(() {
final info = 'onError: ${errorCodeType.name}';
logger.e(info);
});
}
},
onJoinChannelSuccess: (RtcConnection connection, int elapsed) {
setState(() {
final info =
'onJoinChannel: ${connection.channelId}, uid: ${connection.localUid}';
logger.i(info);
});
},
onLeaveChannel: (RtcConnection rtcConnection, RtcStats rtcStats) {
setState(() {
logger.i('onLeaveChannel');
_users.clear();
});
},
onUserJoined: (RtcConnection connection, int remoteUid, int elapsed) {
setState(() {
isRigning = false;
final info = 'remoteUserJoined: $remoteUid';
logger.i(info);
_users.add(remoteUid);
});
},
onUserOffline: (RtcConnection connection, int remoteUid,
UserOfflineReasonType reason) {
setState(() {
final info =
'remoteUserOffline: $remoteUid , reason: ${reason.index}';
logger.i(info);
_users.remove(remoteUid);
});
},
onFirstRemoteVideoFrame: (connection, uid, width, height, elapsed) {
setState(() {
final info = 'firstRemoteVideoFrame: $uid';
logger.i(info);
});
},
));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Agora Group Video Calling'),
),
backgroundColor: Colors.black,
body: Center(
child: Stack(
children: <Widget>[
Opacity(
opacity: isRigning ? 0.2 : 1,
child: _viewRows(),
),
_toolbar(),
if (isRigning)
Positioned(
top: 100,
left: MediaQuery.of(context).size.width * 0.3,
right: MediaQuery.of(context).size.width * 0.3,
child: Center(
child: Text(
'Ringing...',
style: TextStyle(color: Colors.white, fontSize: 30),
),
))
],
),
),
);
}
/// Helper function to get list of native views
List<Widget> _getRenderViews() {
final List<StatefulWidget> list = [];
list.add(AgoraVideoView(
controller: VideoViewController(
rtcEngine: _engine,
canvas: const VideoCanvas(uid: 0),
)));
_users.forEach((int uid) => list.add(AgoraVideoView(
controller: VideoViewController.remote(
rtcEngine: _engine,
canvas: VideoCanvas(uid: uid),
connection: RtcConnection(channelId: channelName),
),
)));
return list;
}
/// Video view wrapper
Widget _videoView(view) {
return Expanded(child: Container(child: view));
}
/// Video view row wrapper
Widget _expandedVideoRow(List<Widget> views) {
final wrappedViews = views.map<Widget>(_videoView).toList();
return Expanded(
child: Row(
children: wrappedViews,
),
);
}
Widget _viewRows() {
final views = _getRenderViews();
switch (views.length) {
case 1:
return Container(
child: Column(
children: <Widget>[_videoView(views[0])],
));
case 2:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow([views[0]]),
_expandedVideoRow([views[1]])
],
));
case 3:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 3))
],
));
case 4:
return Container(
child: Column(
children: <Widget>[
_expandedVideoRow(views.sublist(0, 2)),
_expandedVideoRow(views.sublist(2, 4))
],
));
default:
}
return Container();
}
Widget _toolbar() {
return Container(
alignment: Alignment.bottomCenter,
padding: const EdgeInsets.symmetric(vertical: 48),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RawMaterialButton(
onPressed: onOffSpeaker,
child: Icon(
isSpeakerOn ? Icons.volume_up_sharp : Icons.volume_off,
color: isSpeakerOn ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: isSpeakerOn ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: _onToggleMute,
child: Icon(
muted ? Icons.mic_off : Icons.mic,
color: muted ? Colors.white : Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: muted ? Colors.blueAccent : Colors.white,
padding: const EdgeInsets.all(12.0),
),
RawMaterialButton(
onPressed: () => _onCallEnd(context),
child: Icon(
Icons.call_end,
color: Colors.white,
size: 35.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.redAccent,
padding: const EdgeInsets.all(15.0),
),
RawMaterialButton(
onPressed: _onSwitchCamera,
child: Icon(
Icons.switch_camera,
color: Colors.blueAccent,
size: 20.0,
),
shape: CircleBorder(),
elevation: 2.0,
fillColor: Colors.white,
padding: const EdgeInsets.all(12.0),
)
],
),
);
}
void _onToggleMute() {
setState(() {
muted = !muted;
});
_engine.muteLocalAudioStream(muted);
}
void _onCallEnd(BuildContext context) {
Navigator.pop(context);
}
void _onSwitchCamera() {
_engine.switchCamera();
}
Future onOffSpeaker() async {
setState(() {
isSpeakerOn = !isSpeakerOn;
});
await _engine.setEnableSpeakerphone(isSpeakerOn);
}
}
I used Logger package to view logs and I found out the error occurs in this piece of code
await _engine.initialize(RtcEngineContext(
appId: appID,
));
The app uses agora_rtc_engine: ^6.1.0 and defined in pubspec.yaml file
In Agora SDK, there must be a late field requestPort which is accessed before the initialization.
It seems already similar issue raised in Agora SDK github repo. But its closed.
You can open a new issue or reopen existing with steps to reproduce the issue.
I'm new to flutter and making my first webview app. Here I'm trying to add a spinner every time when a user tries to click the link or page load. I want to make spinner background opacity a bit low just like the given example, but opacity doesn't work at all.
My approach
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white.withOpacity(0.5),
child: Center(
child: SpinKitDualRing(
color: Colors.pinkAccent,
size: 45.0,
controller: AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
),
),
),
)
I'm using here flutter_spinkit package as a spinner.
Full code
// ignore_for_file: prefer_const_constructors
// ignore: use_key_in_widget_constructors
import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:splash_screen_view/SplashScreenView.dart';
void main(){
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle(
statusBarColor: Color(0xff1e2229)
));
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
Widget spalshfirst = SplashScreenView(
navigateRoute: WebViewClass(),
duration: 3000,
imageSize: 80,
imageSrc: 'assets/splash.png',
text: "Food Delivery",
textType: TextType.TyperAnimatedText,
textStyle: TextStyle(
fontSize: 25.0,
),
colors: const [
Colors.purple,
Colors.blue,
Colors.yellow,
Colors.red,
],
backgroundColor: Colors.white,
);
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: spalshfirst
)
);
}
}
class WebViewClass extends StatefulWidget {
WebViewState createState() => WebViewState();
}
class WebViewState extends State<WebViewClass> with TickerProviderStateMixin{
late WebViewController _controller;
final Completer<WebViewController> _controllerCompleter =
Completer<WebViewController>();
//Make sure this function return Future<bool> otherwise you will get an error
Future<bool> _onWillPop(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
return Future.value(true);
}
}
#override
void initState() {
super.initState();
// Enable hybrid composition.
if (Platform.isAndroid) WebView.platform = SurfaceAndroidWebView();
}
bool isLoading = false;
final key = UniqueKey();
int position = 0;
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () => _goBack(context),
child: Scaffold(
resizeToAvoidBottomInset: false,
appBar: null,
body: SafeArea(
child: IndexedStack(
index: position,
children: [
WebView(
initialUrl: 'https://google.com',
javascriptMode: JavascriptMode.unrestricted,
key: key,
onPageStarted: (value) {
setState(() {
position = 1;
});
},
onPageFinished: (value) {
setState(() {
position = 0;
});
},
onWebViewCreated: (WebViewController webViewController) {
_controllerCompleter.future
.then((value) => _controller = value);
_controllerCompleter.complete(webViewController);
},
),
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
color: Colors.white.withOpacity(0.5),
child: Center(
child: SpinKitDualRing(
color: Colors.pinkAccent,
size: 45.0,
controller: AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1200),
),
),
),
)
],
),
),
),
);
}
Future<bool> _goBack(BuildContext context) async {
if (await _controller.canGoBack()) {
_controller.goBack();
return Future.value(false);
} else {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text('Do you want to exit from Foodrive?'),
actions: <Widget>[
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('No'),
),
TextButton(
onPressed: () {
SystemNavigator.pop();
},
child: Text('Yes'),
),
],
));
return Future.value(true);
}
}
}
Since the container is containing only the spinner, and not the background widget, settings its opacity won't work,
I'd suggest using the Stack widget with the Opacity widget
Something like this (just a reference point):
return Stack(children: [
Opacity(opacity: 0.5, child: resetOfTheWidgetTree),
Container(child: spinWidgetHere),
]);
I hope you all are doing well today. I have another flutter issue that I have been stuck on for the past few days now. I'm attempting to upload this data to my firestore instance, but my post button never seems to be triggering. I have attempted to print a statement from the method that it evokes, but I can't seem to get that to work either. I'm attempting to create a social media app, and any and all help would be appreciated.
My main goal is to get the post button to execute in upload.dart.
I have also included home.dart since the two classes are connected in terms of performance.
upload.dart
import 'dart:io';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:fluttermedia/models/user.dart';
import 'package:fluttermedia/pages/home.dart';
import 'package:fluttermedia/widgets/progress.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:image/image.dart' as Im;
import 'package:uuid/uuid.dart';
class Upload extends StatefulWidget {
final User currentUser;
Upload({this.currentUser});
#override
_UploadState createState() => _UploadState();
}
class _UploadState extends State<Upload> {
TextEditingController locationController = TextEditingController();
TextEditingController captionController = TextEditingController();
File file;
bool isUploading = false;
String postId = Uuid().v4();
handleChooseFromGallery() async{
Navigator.pop(context);
File file = await ImagePicker.pickImage(source: ImageSource.gallery);
setState(() {
this.file = file;
});
}
handleTakePhoto() async {
Navigator.pop(context);
File file = await ImagePicker.pickImage(source: ImageSource.camera,maxHeight: 675,maxWidth: 960);
setState(() {
this.file = file;
});
}
selectImage(parentContext){
return showDialog(
context: parentContext,
builder: (context) {
return SimpleDialog(
title: Text("Create Post"),
children: <Widget>[
SimpleDialogOption(
child: Text("Photo With Camera"),
onPressed: handleTakePhoto,
),
SimpleDialogOption(
child: Text("Image from Gallery"),
onPressed: handleChooseFromGallery,
),
SimpleDialogOption(
child: Text("Cancel"),
onPressed: () => Navigator.pop(context),
),
],
);
}
);
}
Container buildSplashScreen(){
return Container(
color: Theme.of(context).accentColor.withOpacity(0.6),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SvgPicture.asset('assets/images/upload.svg',height: 260.0,),
Padding(
padding: EdgeInsets.only(top:20.0),
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0),
),
child: Text(
"Upload Image",
style: TextStyle(
color: Colors.white,
fontSize: 22.0,
),
),
color: Colors.deepOrange,
onPressed: () => selectImage(context),
),
)
],
),
);
}
clearImage(){
setState(() {
file = null;
});
}
//This compresses images for firebase
compressImage() async{
final tempDir = await getTemporaryDirectory();
final path = tempDir.path;
Im.Image imageFile = Im.decodeImage(file.readAsBytesSync());
final compressedImageFile = File('$path/img_$postId.jpg')..writeAsBytesSync(Im.encodeJpg(imageFile,quality: 85));
setState(() {
file = compressedImageFile;
});
}
Future<String> uploadImage(imageFile) async{
StorageUploadTask uploadTask = storageRef.child("post_$postId.jpg").putFile(imageFile);
StorageTaskSnapshot storageSnap = await uploadTask.onComplete;
String downloadUrl = await storageSnap.ref.getDownloadURL();
return downloadUrl;
}
//upload new info to firestore that creates a new collection
createPostInFirestore({String mediaUrl, String location, String description}){
postsRef.document(widget.currentUser.id)
.collection("userPosts")
.document(postId)
.setData({
"postId": postId,
"ownerId": widget.currentUser.id,
"username": widget.currentUser.username,
"mediaUrl": mediaUrl,
"description": description,
"location": location,
"timestamp": timeStamp,
"likes":{}
});
}
//Getting the info from the caption, location and pic
handleSubmit() async{
setState(() {
isUploading = true;
});
await compressImage();
String mediaUrl = await uploadImage(file);
createPostInFirestore(
mediaUrl: mediaUrl,
location: locationController.text,
description: captionController.text,
);
captionController.clear();
locationController.clear();
setState(() {
file = null;
isUploading = false;
});
}
Scaffold buildUploadForm(){
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white70,
leading: IconButton(
icon: Icon(Icons.arrow_back,color: Colors.black,),
onPressed: clearImage,
),
title: Text(
"Caption Post",
style: TextStyle(color: Colors.black),
),
actions: <Widget>[
FlatButton(
onPressed: () => isUploading ? null : () => handleSubmit(),
child: Text(
"Post",
style: TextStyle(
color: Colors.blueAccent,
fontWeight: FontWeight.bold,
fontSize: 20.0,
),
),
)
],
),
body: ListView(
children: <Widget>[
isUploading ? linearProgress(context):Text(""),
Container(
height: 220.0,
width: MediaQuery.of(context).size.width*0.8,
child: Center(
child: AspectRatio(
aspectRatio: 16/9,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
fit: BoxFit.cover,
image: FileImage(file),
)
),
),
),
),
),
Padding(
padding: EdgeInsets.only(top:10),
),
ListTile(
leading: CircleAvatar(
backgroundImage: CachedNetworkImageProvider(widget.currentUser.photoUrl),
),
title: Container(
width: 250.0,
child: TextField(
controller: captionController,
decoration: InputDecoration(
hintText: "Write a Caption...",
border: InputBorder.none,
),
),
),
),
Divider(),
ListTile(
leading: Icon(Icons.pin_drop,color: Colors.orange,size: 35.0),
title: Container(
width: 250.0,
child: TextField(
controller: locationController,
decoration: InputDecoration(
hintText: "Where was this photo taken",
border: InputBorder.none,
),
),
),
),
Container(
width: 200.0,
height: 100.0,
alignment: Alignment.center,
child: RaisedButton.icon(
label: Text(
"Use Current Location",
style: TextStyle(color: Colors.white),
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30.0),
),
color: Colors.blue,
onPressed: () => print("Get user location"),
icon: Icon(
Icons.my_location,
color: Colors.white,
),
),
)
],
),
);
}
#override
Widget build(BuildContext context) {
return file == null ? buildSplashScreen() : buildUploadForm();
}
}
home.dart
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttermedia/models/user.dart';
import 'package:fluttermedia/pages/activity_feed.dart';
import 'package:fluttermedia/pages/create_account.dart';
import 'package:fluttermedia/pages/profile.dart';
import 'package:fluttermedia/pages/search.dart';
import 'package:fluttermedia/pages/upload.dart';
import 'package:google_sign_in/google_sign_in.dart';
final GoogleSignIn googleSignIn = GoogleSignIn();
final StorageReference storageRef = FirebaseStorage.instance.ref();
final usersRef = Firestore.instance.collection('users');
final postsRef = Firestore.instance.collection('posts');
final DateTime timeStamp = DateTime.now();
User currentUser;
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
bool isAuth = false;
PageController pageController;
int pageIndex = 0;
#override
void initState() {
super.initState();
pageController = PageController();
// Detects if user signs in
googleSignIn.onCurrentUserChanged.listen((account) {
handleSignIn(account);
}, onError: (err){
print('Error sigining in: $err');
});
//Reauthenticate user when app is opened
googleSignIn.signInSilently(suppressErrors: false)
.then((account) =>
handleSignIn(account)).catchError((err){
print('Error signing in on retry: $err');
});
}
#override
Widget build(BuildContext context) {
return isAuth ? buildAuthScreen() : buildUnAuthScreen();
}
#override
void dispose(){
pageController.dispose();
super.dispose();
}
//Helper Functions
//The sign in section of the code
handleSignIn(GoogleSignInAccount account){
if(account != null){
createUserInFirestore();
setState(() {
isAuth = true;
});
}else{
setState(() {
isAuth = false;
});
}
}
login(){
googleSignIn.signIn();
}
logout(){
googleSignIn.signOut();
}
onPageChanged(int pageIndex){
setState(() {
this.pageIndex = pageIndex;
});
}
createUserInFirestore() async{
// 1) Check if user exists in users collection in database (According to id)
final GoogleSignInAccount user = googleSignIn.currentUser;
DocumentSnapshot doc = await usersRef.document(user.id).get();
if(!doc.exists){
// 2) If the user doesn't exist, take them to create account page
final username = await Navigator.push(context, MaterialPageRoute(builder: (context) => CreateAccount()));
// 3) get username from create account, use it to make new user document in users collection
usersRef.document(user.id).setData({
"id":user.id,
"username":username,
"photoUrl": user.photoUrl,
"email":user.email,
"displayName": user.displayName,
"bio":"",
"timeStamp": timeStamp,
});
doc = await usersRef.document(user.id).get();
}
currentUser = User.fromDocument(doc);
//print(currentUser);
//print(currentUser.username);
}
onTap(int pageIndex){
//This what you would use to animate in between the different screens
pageController.animateToPage(
pageIndex,
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut
);
}
//UI Code
Widget buildAuthScreen() {
return Scaffold(
body: PageView(
children: <Widget>[
//Timeline(),
RaisedButton(
child: Text('Logout'),
onPressed: logout,
),
ActivityFeed(),
Upload(currentUser: currentUser),
Search(),
Profile(),
],
controller: pageController,
onPageChanged: onPageChanged,
physics: NeverScrollableScrollPhysics(),
),
bottomNavigationBar: CupertinoTabBar(
currentIndex: pageIndex,
onTap: onTap,
activeColor: Theme.of(context).primaryColor,
items: [
BottomNavigationBarItem(icon: Icon(Icons.whatshot),),
BottomNavigationBarItem(icon: Icon(Icons.notifications_active),),
BottomNavigationBarItem(icon: Icon(Icons.photo_camera, size: 34.0,),),
BottomNavigationBarItem(icon: Icon(Icons.search),),
BottomNavigationBarItem(icon: Icon(Icons.account_circle),),
],
),
);
/*return RaisedButton(
child: Text('Logout'),
onPressed: logout,
);*/
}
Scaffold buildUnAuthScreen() {
return Scaffold(
body: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: [
Theme.of(context).primaryColor,
Theme.of(context).accentColor,
]
)
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Text('FlutterMedia',
style: TextStyle(
fontFamily: "Signatra",
fontSize: 90.0,
color: Colors.white
),
),
GestureDetector(
onTap:() => login(),
child: Container(
width: 260,
height: 60,
decoration: BoxDecoration(
image: DecorationImage(
image: AssetImage('assets/images/google_signin_button.png'),
fit: BoxFit.cover,
),
),
),
)
],
),
),
);
}
}
onPressed: () => isUploading ? null : () => handleSubmit(),
Well, there's your problem. You meant to have a tertiary condition that makes onPressed call handleSubmit when isUploading is false. Instead, you have made onPressed into a function that returns a function.
To hopefully make that more clear, let's blow this function up into proper non-lambda functions and if/else blocks:
onPressed: () {
if (isUploading) {
return null;
} else {
return () {
handleUpload();
}
}
}
So consider what happens when the button is pressed. It calls the outer function, which checks isUploading. If true, the function returns null, and if false, it returns another function that, if called, calls handleUpload. So how this plays out is that onPressed will never be null (it just returns null sometimes) and handleUpload never gets called (since the inner function that is returned is never then called itself).
Remove the outer lambda and it will work:
onPressed: isUploading ? null : () => handleSubmit(),
In the code below, I seek to make users vote only once in my Voting App.
At the moment users can vote more than once. I have created hasVoted field(a map with the UID of users and true as a value to indicate the user has voted) in the item being voted for as shown in my Firestore backend, however this does not seem to work as i want it to. What could be wrong. Does anyone here know a way around this?
Please i am new to flutter and dart so kindly forgive petty mistake that i make in posting this question
Below is my code
import 'dart:async';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:compuvote/models/finance_model.dart';
import 'package:compuvote/routes/home_page.dart';
import 'package:compuvote/routes/transitionroute.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:charts_flutter/flutter.dart' as charts;
class FinanceResult extends StatefulWidget {
#override
_FinanceResultState createState() {
return _FinanceResultState();
}
}
class _FinanceResultState extends State<FinanceResult> {
List<charts.Series<Record, String>> _seriesBarData;
List<Record> mydata;
_generateData(mydata) {
_seriesBarData = List<charts.Series<Record, String>>();
_seriesBarData.add(
charts.Series(
domainFn: (Record record, _) => record.name.toString(),
measureFn: (Record record, _) => record.totalVotes,
//colorFn: (Record record, _) => record.color,
id: 'Record',
data: mydata,
// Set a label accessor to control the text of the arc label.
labelAccessorFn: (Record row, _) => '${row.name}: ${row.totalVotes}',
colorFn: (_, __) => charts.MaterialPalette.cyan.shadeDefault,
fillColorFn: (_, __) => charts.MaterialPalette.transparent,
),
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Finance Result'),
leading: IconButton(
icon: Icon(Icons.home),
onPressed: () {
Navigator.push(context, TransitionPageRoute(widget: HomePage()));
},
),
),
body: Container(
color: Colors.grey.shade100,
child: _buildBody(context),
));
}
/// ****** This code is suppose to build the body ***********/
Widget _buildBody(BuildContext context) {
return Column(
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('finance').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: LinearProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(context).primaryColor,
),
),
);
} else {
List<Record> finance = snapshot.data.documents
.map((documentSnapshot) =>
Record.fromMap(documentSnapshot.data))
.toList();
return _buildChart(context, finance);
}
},
),
],
);
}
Widget _buildChart(BuildContext context, List<Record> recorddata) {
mydata = recorddata;
_generateData(mydata);
return Padding(
padding: EdgeInsets.all(8.0),
child: Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height / 1.2,
child: Center(
child: Column(
children: <Widget>[
SizedBox(
height: 10.0,
),
Expanded(
child: charts.PieChart(
_seriesBarData,
animate: true,
animationDuration: Duration(seconds: 3),
//For adding labels to the chart
defaultRenderer: new charts.ArcRendererConfig(
strokeWidthPx: 2.0,
arcWidth: 100,
arcRendererDecorators: [
// <-- add this to the code
charts.ArcLabelDecorator(
labelPosition: charts.ArcLabelPosition.auto,
labelPadding: 3,
showLeaderLines: true,
insideLabelStyleSpec: charts.TextStyleSpec(
color: charts.Color.white,
fontSize: 12,
),
outsideLabelStyleSpec: charts.TextStyleSpec(
color: charts.Color.black,
fontSize: 12,
),
),
]),
),
),
Container(
width: MediaQuery.of(context).size.width / 3.5,
height: 1,
color: Colors.black38,
),
Expanded(
child: Scaffold(
backgroundColor: Colors.grey.shade100,
body: _castVote(context),
),
),
],
),
),
),
);
}
/// ****** This code is suppose to link to the Firestore collection ***********/
Widget _castVote(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('finance').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData)
return Center(
child: LinearProgressIndicator(
valueColor: AlwaysStoppedAnimation(
Theme.of(context).primaryColor,
),
),
);
return _buildList(context, snapshot.data.documents);
},
);
}
/// ****** This code is suppose to build the List ***********/
Widget _buildList(BuildContext context, List<DocumentSnapshot> snapshot) {
return ListView(
padding: const EdgeInsets.only(top: 20.0),
// ignore: missing_return
children: snapshot.map((data) => _buildListItem(context, data)).toList(),
);
}
/// ****** This code is suppose to build the List Items ***********/
Widget _buildListItem(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
return Padding(
key: ValueKey(record.name),
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 8.0),
child: Card(
elevation: 2,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey.shade100),
borderRadius: BorderRadius.circular(5.0),
),
child: ListTile(
title: Text(record.name),
trailing: Text(record.totalVotes.toString()),
onTap: () => {
_checkHasVoted(context, data),
}
//onTap: () => {record.reference.updateData({'votes': record.votes + 1}),
),
),
),
);
}
/// ****** This code is suppose to force users to vote only once ***********/
Widget _checkHasVoted(BuildContext context, DocumentSnapshot data) {
final record = Record.fromSnapshot(data);
StreamSubscription<DocumentSnapshot> subscription;
final DocumentReference documentReference =
Firestore.instance.collection("finance").document() as DocumentReference;
#override
void initState() {
super.initState();
subscription = documentReference.snapshots().listen((datasnapshot) {
if ((datasnapshot.data
.containsKey(FirebaseAuth.instance.currentUser()))) {
setState(() {
return Text("Sorry you have voted in this category already");
});
}
else if (!datasnapshot.data.containsKey(FirebaseAuth.instance.currentUser())){
setState(() {
record.reference.updateData({'votes':record.totalVotes + 1});
});
}
});
}
}
}
Hi I have a screen for adding images. I use the multi_image_picker for picking the images. Upon selection of the images, all images picked will be shown in a grid view with a FloatingActionButton on top of each image for removal of each image. The problem is, when I click the button for removing an image, the last image is removed not the one I clicked on. Has anyone experienced this?
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:krakatoa/api/FileApi.dart';
import 'package:krakatoa/components/AssetView.dart';
import 'package:krakatoa/config/Themes.dart' as themes;
import 'package:krakatoa/mixins/SnackBarMixin.dart';
import 'package:krakatoa/podos/User.dart';
import 'package:krakatoa/utils/Common.dart' as common;
import 'package:multi_image_picker/multi_image_picker.dart';
class AddImageScreen extends StatefulWidget {
final List<String> currentImageUrls;
final String postUrl;
AddImageScreen({this.currentImageUrls, #required this.postUrl});
#override
State<StatefulWidget> createState() => _AddImageState();
}
class _AddImageState extends State<AddImageScreen> with SnackBarMixin {
List<Asset> _images = [];
User _user;
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('Add images'),
),
body: Padding(
padding: const EdgeInsets.all(10.0),
child: ListView(
children: <Widget>[
Container(
padding: EdgeInsets.only(bottom: 10),
child: Text(
"Upload Images:",
style: Theme.of(context).textTheme.headline,
),
),
Container(
padding: EdgeInsets.only(bottom: 10),
child: GridView.count(
shrinkWrap: true,
crossAxisSpacing: 3,
mainAxisSpacing: 3,
crossAxisCount: 3,
children: _renderPickedImages(),
),
),
Container(
child: FlatButton(
child: Text('UPLOAD'),
padding: EdgeInsets.symmetric(vertical: 15),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
color: themes.primaryColor,
onPressed: _onUploadBtnPress,
),
),
],
),
),
);
}
#override
void dispose() {
super.dispose();
for (var image in _images) {
image.release();
}
}
#override
void initState() {
super.initState();
common.getCurrentUser().then((user) {
if (user != null) {
_user = user;
} else {
_user = User();
}
});
}
void showProgressAlert() {
showDialog(
context: context,
builder: (context) {
return WillPopScope(
onWillPop: () async => false,
child: AlertDialog(
content: ListTile(
leading: CircularProgressIndicator(
value: null,
),
title: Text('Processing...'),
),
),
);
},
barrierDismissible: false,
);
}
void _onAddImageBtnPress() async {
List<Asset> resultList;
try {
resultList = await MultiImagePicker.pickImages(maxImages: 5, enableCamera: true);
} on PlatformException catch (e) {
debugPrint("AddImageScreen._onAddImageBtnPress: ${e.toString()}");
}
if (!mounted) return;
if (resultList.isNotEmpty) {
setState(() {
_images.addAll(resultList);
});
}
}
void _onUploadBtnPress() {
if (_images.isNotEmpty) {
showProgressAlert();
_uploadImages();
} else {
showSnackBarMessage("No images to upload", seconds: 5);
}
}
void _removeImage(int index, Asset image) {
debugPrint("INDEX: $index");
debugPrint("Orig Image: ${_images[index].hashCode}");
setState(() {
_images.removeAt(index);
});
}
List<Widget> _renderPickedImages() {
List<Widget> imageWidgets = [];
imageWidgets.add(InkWell(
child: Container(
decoration: BoxDecoration(
border: Border.all(
width: 1,
color: Colors.grey,
),
),
child: Center(
child: Icon(
Icons.add,
size: 60,
color: Colors.grey,
),
),
),
onTap: _onAddImageBtnPress,
));
var ctr = 0;
for (var image in _images) {
imageWidgets.add(Container(
child: Stack(
fit: StackFit.expand,
overflow: Overflow.visible,
children: <Widget>[
AssetView(image),
Positioned(
bottom: 0,
right: 0,
child: _ImageRemoveButton(
index: ctr,
removeItem: _removeImage,
image: image,
),
),
],
),
));
ctr++;
}
return imageWidgets;
}
Future<void> _uploadImages() async {
if (_user.id <= 0) {
showSnackBarMessage("User is not logged in");
Navigator.of(context).pop("User is not logged in");
return;
}
try {
await FileApi.uploadImages(widget.postUrl, _images);
Navigator.of(context).pop();
Navigator.of(context).pop("Success");
} on Exception catch (e) {
debugPrint(e.toString());
showSnackBarMessage(e.toString());
Navigator.of(context).pop(e.toString());
}
}
}
class _ImageRemoveButton extends StatelessWidget {
final Function removeItem;
final Asset image;
final int index;
_ImageRemoveButton({this.removeItem, this.index, this.image});
#override
Widget build(BuildContext context) {
return FloatingActionButton(
backgroundColor: Colors.white,
mini: true,
heroTag: "ImageAction_$index",
isExtended: false,
child: Icon(
Icons.close,
size: 15,
color: Colors.black,
),
onPressed: _onPress,
);
}
void _onPress() {
debugPrint("${this.hashCode}");
debugPrint("Passed Image: ${image.hashCode}");
removeItem(index, image);
}
}
add key: Key(YOUR_LIST.length.toString()),
having KEY helps with the update of the ListView
that worked for me on ListView.builder