Related
I am developing a voice/video-calling app using Flutter and Agora rtc engine (v 5.3.1). I have followed all the necessary steps for generating token and joining a channel. But, I can't see the local camera view in the UI and the logs gives me warning saying: "onWarning warn 8 msg invalid view for local video". Any leads would be big help, thanks in advance.
Logs:
W/spdlog (30579): [2023-01-04 21:02:33.375] [0] [warning] /tmp/jenkins/IRIS-SDK/rtc/cxx/src/internal/rtc_engine_event_handler.cc:43 onWarning warn 8 msg invalid view for local video
W/spdlog (30579): [2023-01-04 21:02:33.375] [0] [warning] /tmp/jenkins/IRIS-SDK/rtc/cxx/src/internal/rtc_engine_event_handler.cc:43 onWarning warn 16
msg nullptr
I/spdlog (30579): [2023-01-04 21:02:33.375] [0] [info] /tmp/jenkins/IRIS-SDK/rtc/cxx/src/internal/rtc_engine_event_handler.cc:72 onJoinChannelSuccess
channel b8667da0-8c6a-11ed-a9fb-578e8ad35bd6 uid 1
W/spdlog (30579): [2023-01-04 21:02:33.377] [0] [warning] /tmp/jenkins/IRIS-SDK/rtc/cxx/src/internal/rtc_engine_event_handler.cc:43 onWarning warn 16
msg nullptr
Call Screen Widget:
imports...
String baseUrl = 'https://...';
class CallScreen extends ConsumerStatefulWidget {
final Call call;
const CallScreen({Key? key, required this.call, }) : super(key: key);
#override
_CallScreenState createState() => _CallScreenState();
}
class _CallScreenState extends ConsumerState<CallScreen> {
int uid = 1;
List<int> _remoteUids = [];
bool isJoined = false,
switchCamera = true,
openMicrophone = true,
enableSpeakerphone = false;
late bool openCamera;
late RtcEngine _engine;
#override
void initState() {
initAgora();
openCamera = widget.call.isVideoCall;
super.initState();
}
Future<String?> _getToken() async {
String url = baseUrl + "/rtc/" + widget.call.callId + "/publisher/uid/" + uid.toString() + "/";;
String? token;
try{
var resp = await http.get(Uri.parse(url));
if(resp.statusCode == 200){
token = jsonDecode(resp.body)['rtcToken'];
return token;
}
else{
showMySnackBar(context: context, content: "Token Status ERR: "+jsonDecode(resp.body)['message']);
return null;
}
}
catch(e){
showMySnackBar(context: context, content: "Token Err: "+e.toString());
return null;
}
}
void _joinChannel() async {
String? token = await _getToken();
if(token != null){
await _engine.joinChannel(token, widget.call.callId, null, uid);
}
else{
showMySnackBar(context: context, content: 'Token is null!');
}
}
void _leaveChannel() async {
ref.read(callControllerProvider).endCall(
widget.call.callerId,
widget.call.receiverId,
context,
widget.call.isGroupChat
);
if(widget.call.isVideoCall) await _engine.stopPreview();
await _engine.leaveChannel();
}
void _switchCamera() {
_engine.switchCamera()
.then((value) {
setState(() {
switchCamera = !switchCamera;
});
})
.catchError((err) {
//print('switchCamera $err');
});
}
void _switchMicrophone() async {
// await _engine.muteLocalAudioStream(!openMicrophone);
await _engine.enableLocalAudio(!openMicrophone)
.then((value) {
setState(() {
openMicrophone = !openMicrophone;
});
})
.catchError((err) {
// print('enableLocalAudio $err');
});
}
void _switchSpeakerphone() {
_engine.setEnableSpeakerphone(!enableSpeakerphone)
.then((value) {
setState(() {
enableSpeakerphone = !enableSpeakerphone;
});
})
.catchError((err) {
//print('setEnableSpeakerphone $err');
});
}
void initAgora() async {
try{
await [Permission.microphone, Permission.camera].request();
_engine = await RtcEngine.createWithContext(RtcEngineContext(AgoraConfig.appId));
_engine.setEventHandler(
RtcEngineEventHandler(
warning: (warn) {
showMySnackBar(context: context, content: "Warn: "+warn.name);
},
error: (err) {
showMySnackBar(context: context, content: 'OnErr event: '+err.name);
},
joinChannelSuccess: (String channel, int userId, int elapsed) {
// print("local user ${connection.localUid} joined");
if(mounted){
setState(() {
isJoined = true;
uid = userId;
});
}
showMySnackBar(context: context, content: 'You ($userId) have joined the call!');
},
userJoined: (int remoteUid, int elapsed) {
//debugPrint("remote user $remoteUid joined");
if(mounted){
setState(() {
_remoteUids.add(remoteUid);
});
}
},
userOffline: (int remoteUid, UserOfflineReason reason) {
//debugPrint("remote user $remoteUid left channel");
if(mounted){
setState(() {
_remoteUids.removeWhere((element) => element == remoteUid);
});
}
},
leaveChannel: (stats) {
if(mounted){
setState(() {
isJoined = false;
if(!widget.call.isGroupChat || _remoteUids.length == 1){
_remoteUids.clear();
}
});
}
},
// onTokenPrivilegeWillExpire: (RtcConnection connection, String token) {
// debugPrint('[onTokenPrivilegeWillExpire] connection: ${connection.toJson()}, token: $token');
// },
),
);
await _engine.setChannelProfile(ChannelProfile.LiveBroadcasting);
//await _engine.setClientRole(ClientRole.Broadcaster);
await _engine.enableVideo();
if(widget.call.isVideoCall){
await _engine.startPreview();
}
else{
await _engine.muteLocalVideoStream(true);
await _engine.muteAllRemoteVideoStreams(true);
}
_joinChannel();
}
catch(e){
showMySnackBar(context: context, content: "Init Err: "+e.toString());
}
}
#override
void dispose() {
_leaveChannel();
_engine.destroy();
super.dispose();
}
// Display remote user's video
Widget _remoteVideo() {
if (_remoteUids.isNotEmpty) {
//TODO check for video on or off or if video call:
return rtc_remote_view.SurfaceView(
uid: _remoteUids[0],
channelId: widget.call.callId,
);
}
else {
return const Text(
'Please wait for others to join',
textAlign: TextAlign.center,
);
}
}
#override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async { _leaveChannel(); return true; },
child: Scaffold(
backgroundColor: Colors.white,
body: Stack(
children: [
Center(
child: _remoteVideo(),
),
Align(
alignment: Alignment.bottomRight,
child: Padding(
padding: const EdgeInsets.only(right: 18.0, bottom: 12),
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(12)),
child: ColoredBox(
color: Colors.grey.shade200,
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 125, maxHeight: 175),
child: AspectRatio(
aspectRatio: 9/16,
child: Center(
child: isJoined
?
( //TODO: check for video on or off or if video call:
openCamera
? rtc_local_view.SurfaceView(
channelId: widget.call.callId,
)
: const Icon(
Icons.videocam_off_rounded,
color: Colors.black,
size: appActionsIconsSize,
)
)
: const CircularProgressIndicator(),
),
),
),
),
),
),
),
],
),
),
);
}
}
I found the issue: I was not setting the ClientRoleType correctly and that caused error in finding local view. One needs to define ClientRoleType (based on your logic) and ChannelProfileType.broadcast and everything seems to work.
The logic in the program I wrote is that it is like Twitter. There will be photo sharing and text sharing. However, there is an error in my codes and when I do not share photos, I get the error 'Null check-operator used on null value'. How can I change this?
When I do not share photos, I get the error below.
These are my codes. I would appreciate it if you could tell me the changes I need to make.
add_post_screenn
class AddPostScreen extends StatefulWidget {
const AddPostScreen({Key? key}) : super(key: key);
#override
State<AddPostScreen> createState() => _AddPostScreenState();
}
class _AddPostScreenState extends State<AddPostScreen> {
Uint8List? _file;
final TextEditingController _descriptionController = TextEditingController();
bool _isLoading = false;
void postImage(
String uid,
String username,
String profImage,
) async {
setState(() {
_isLoading = true;
});
try {
String res = await FirestoreMethods().uploadPost(
_descriptionController.text, _file!, uid, username, profImage);
if (res == 'succes') {
setState(() {
_isLoading = false;
});
showSnackBar('Posted!', context);
clearImage();
} else {
showSnackBar(res, context);
}
} catch (e) {
showSnackBar(e.toString(), context);
}
}
_selectImage(BuildContext context) async {
return showDialog(
context: context,
builder: (context) {
return SimpleDialog(
title: const Text('Create a Post'),
children: [
SimpleDialogOption(
padding: const EdgeInsets.all(20),
child: const Text('Take a photo'),
onPressed: () async {
Navigator.of(context).pop();
Uint8List file = await pickImage(
ImageSource.camera,
);
setState(() {
_file = file;
});
},
),
SimpleDialogOption(
padding: const EdgeInsets.all(20),
child: const Text('Chose from gallery'),
onPressed: () async {
Navigator.of(context).pop();
Uint8List file = await pickImage(
ImageSource.gallery,
);
setState(() {
_file = file;
});
},
),
SimpleDialogOption(
padding: const EdgeInsets.all(20),
child: const Text('Cancel'),
onPressed: () {
Navigator.of(context).pop();
}),
],
);
});
}
void clearImage() {
_file = null;
}
#override
void dispose() {
super.dispose();
_descriptionController.dispose();
}
#override
Widget build(BuildContext context) {
final User user = Provider.of<UserProvider>(context).getUser;
//yorum satırı olacak
return _file == null
? Center(
child: IconButton(
icon: const Icon(Icons.upload),
onPressed: () => _selectImage(context),
),
)
// //buraya kadar
: Scaffold(
appBar: AppBar(
backgroundColor: mobileBackgroundColor,
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: clearImage,
),
title: const Text('Paylaşım'),
centerTitle: false,
actions: [
TextButton(
onPressed: () => postImage(
user.uid,
user.username,
user.photoUrl,
),
child: const Text(
'Paylaş',
style: TextStyle(
color: Colors.blueAccent,
fontWeight: FontWeight.bold,
fontSize: 16,
),
))
],
),
body: Column(
children: [
_isLoading
? const LinearProgressIndicator()
: const Padding(
padding: EdgeInsets.only(
top: 0,
),
),
const Divider(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
CircleAvatar(
backgroundImage: NetworkImage(user.photoUrl),
),
SizedBox(
width: MediaQuery.of(context).size.width * 0.45,
child: TextField(
controller: _descriptionController,
decoration: const InputDecoration(
hintText: 'Hayallerini Yaz!',
border: InputBorder.none,
),
maxLines: 8,
),
),
SizedBox(
height: 45,
width: 45,
child: AspectRatio(
aspectRatio: 487 / 451,
child: Container(
decoration: BoxDecoration(
image: DecorationImage(
image: MemoryImage(_file!),
fit: BoxFit.fill,
alignment: FractionalOffset.topCenter,
)),
),
),
),
const Divider(),
],
),
],
),
);
}
}
firestore_methods
class FirestoreMethods {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
//upload post
Future<String> uploadPost(
String description,
Uint8List file,
String uid,
String username,
String profImage,
) async {
String res = 'Some error occurred';
try {
String photoUrl =
await StorageMethods().uploadImageToStorage('posts', file, true);
String postId = const Uuid().v1();
Post post = Post(
description: description,
uid: uid,
username: username,
postId: postId,
datePublished: DateTime.now(),
postUrl: photoUrl,
porfImage: profImage,
likes: [],
);
_firestore.collection('posts').doc(postId).set(
post.toJson(),
);
res = 'succes';
} catch (err) {
res = err.toString();
}
return res;
}
Future<void> LikePost(String postId, String uid, List likes) async {
try {
if (likes.contains(uid)) {
await _firestore.collection('posts').doc(postId).update({
'likes': FieldValue.arrayRemove([uid]),
});
} else {
await _firestore.collection('posts').doc(postId).update({
'likes': FieldValue.arrayUnion([uid]),
});
}
} catch (e) {
print(
e.toString(),
);
}
}
Future<void> postComment(String postId, String text, String uid, String name,
String profilePic) async {
try {
if (text.isNotEmpty) {
String commentId = const Uuid().v1();
await _firestore
.collection('posts')
.doc(postId)
.collection('comments')
.doc(commentId)
.set({
'profilePic': profilePic,
'name': name,
'uid': uid,
'text': text,
'commentId': commentId,
'datePublished': DateTime.now(),
});
} else {
print('Text is empty');
}
} catch (e) {
print(
e.toString(),
);
}
}
storage.methods
class StorageMethods {
final FirebaseAuth _auth = FirebaseAuth.instance;
final FirebaseStorage _storage = FirebaseStorage.instance;
// firebase deposuna resim ekleme firebase deposuna resim ekleme
Future<String> uploadImageToStorage(
String childName, Uint8List file, bool isPost) async {
// firebase depolama alanımıza konum oluşturma
Reference ref =
_storage.ref().child(childName).child(_auth.currentUser!.uid);
if (isPost) {
String id = const Uuid().v1();
ref = ref.child(id);
}
// putting in uint8list format -> Upload task like a future but not future
UploadTask uploadTask = ref.putData(file);
TaskSnapshot snap = await uploadTask;
String downloadUrl = await snap.ref.getDownloadURL();
return downloadUrl;
}
}
The error cause comes from the _file!. When posting only text there is going to be no _file as it's going to be null.
To fix this change the references from _file! to just _file and the FirestoreMethods.uploadPost should be the following:
FirestoreMethods.uploadPost - Change file to be nullable and check for null.
Future<String> uploadPost(
String description,
Uint8List? file, // <- Here
String uid,
String username,
String profImage,
) async {
String res = 'Some error occurred';
try {
String photoUrl = file != null // <- Here
? await StorageMethods().uploadImageToStorage('posts', file, true)
: '';
String postId = const Uuid().v1();
Post post = Post(
description: description,
uid: uid,
username: username,
postId: postId,
datePublished: DateTime.now(),
postUrl: photoUrl,
porfImage: profImage,
likes: [],
);
_firestore.collection('posts').doc(postId).set(
post.toJson(),
);
res = 'succes';
} catch (err) {
res = err.toString();
}
return res;
}
_AddPostScreenState.postImage - Change _file! to just _file
void postImage(
String uid,
String username,
String profImage,
) async {
setState(() {
_isLoading = true;
});
try {
String res = await FirestoreMethods().uploadPost(
_descriptionController.text, _file, uid, username, profImage);
if (res == 'succes') {
setState(() {
_isLoading = false;
});
showSnackBar('Posted!', context);
clearImage();
} else {
showSnackBar(res, context);
}
} catch (e) {
showSnackBar(e.toString(), context);
}
}
I think you are getting error in this line
Reference ref =
_storage.ref().child(childName).child(_auth.currentUser!.uid);
Here the current user maybe null. To fix this error for now you can use
Reference ref =
_storage.ref().child(childName).child(_auth.currentUser?.uid);
//Note instead of using ! I have used ? which means currentUser can be null. ! means you are marking that its not null and value is unknown
but please note that you are receiving _auth.currentUser as null. Please check those codes
This function have futureBuilder inside and returns another function that returns ListView.
Also I do not want the user to download information from the Internet every time they visit the page, so I put the information downloaded from the Internet into a static variable in class, so i make if else. If this static variable length != 0 , it return me listView instead of make server request with futureBuilder
function that works 2 times instead of one is ListView body
And also this bug created after i added purchases to my application, maybe something wrong there. I don't have any ideas why this happend
this is my complete code
class AddCheckList extends StatefulWidget {
const AddCheckList({Key? key}) : super(key: key);
#override
_AddCheckListState createState() => _AddCheckListState();
}
class _AddCheckListState extends State<AddCheckList> {
String xQueryReqestForCheckListNames = "element CheckListList {for \$a in PACK/OBJECT where (\$a/#inUse = 'True') order by \$a/#name return element CheckList {attribute name {\$a/#name}, attribute sPaid {\$a/#isPaid},attribute oid {\$a/#oid} }}";
String serverLink = "http...";
int addCheckListMethod = 0;
// if addCheckListMethod == 0, then data will download from server and checkLists will be added from server xml data
// if addCheckListMethod == 1, then data will download from assets, and checkLists will be added from assets xml data with getXmlData function
final InAppPurchase _inAppPurchase = InAppPurchase.instance;
final String _productID = '1d7ea644f690ffa';
bool _available = true;
List<ProductDetails> _products = [];
List<PurchaseDetails> _purchases = [];
StreamSubscription<List<PurchaseDetails>>? _subscription;
#override
void initState() {
final Stream<List<PurchaseDetails>> purchaseUpdated = _inAppPurchase.purchaseStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
setState(() {
_purchases.addAll(purchaseDetailsList);
_listenToPurchaseUpdated(purchaseDetailsList);
});
}, onDone: () {
_subscription!.cancel();
}, onError: (error) {
_subscription!.cancel();
});
_initialize();
super.initState();
}
#override
void dispose() {
_subscription!.cancel();
super.dispose();
}
void _initialize() async {
_available = await _inAppPurchase.isAvailable();
List<ProductDetails> products = await _getProducts(
productIds: Set<String>.from(
[_productID],
),
);
setState(() {
_products = products;
});
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList) {
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
switch (purchaseDetails.status) {
case PurchaseStatus.pending:
// _showPendingUI();
break;
case PurchaseStatus.purchased:
case PurchaseStatus.restored:
// bool valid = await _verifyPurchase(purchaseDetails);
// if (!valid) {
// _handleInvalidPurchase(purchaseDetails);
// }
break;
case PurchaseStatus.error:
print(purchaseDetails.error!);
// _handleError(purchaseDetails.error!);
break;
default:
break;
}
if (purchaseDetails.pendingCompletePurchase) {
await _inAppPurchase.completePurchase(purchaseDetails);
}
});
}
Future<List<ProductDetails>> _getProducts({required Set<String> productIds}) async {
ProductDetailsResponse response = await _inAppPurchase.queryProductDetails(productIds);
return response.productDetails;
}
void _subscribe({required ProductDetails product}) {
final PurchaseParam purchaseParam = PurchaseParam(productDetails: product);
_inAppPurchase.buyNonConsumable(
purchaseParam: purchaseParam,
);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(padding: EdgeInsets.all(14.0), child: listViweBody(addCheckListMethod, serverLink, xQueryReqestForCheckListNames, _products,_subscribe)),
);
}
}
Widget listView(addCheckListMethod, serverLink, product,_subscribe) {
return ListView.separated(
itemCount: CheckListModel.checkListModelNamesFromServer.length,
itemBuilder: (BuildContext context, int index) {
if (CheckListModel.checkListModelNamesFromServer[index].ispaid == false) {
return InkWell(
onTap: () async {
CheckListModel checkList = CheckListModel('', 0, '', 0, 0, 0, '', [], '');
if (addCheckListMethod == 0) {
String xQueryReqestForCheckList = 'for \$a in PACK/OBJECT where \$a/#name="${CheckListModel.checkListModelNamesFromServer[index].name}" return \$a';
var data = await CheckListModel.getDataFromServer(xQueryReqestForCheckList, serverLink);
CheckListModel.addCheckListFromServer(checkList, data);
} else {
CheckListModel.addCheckList(index, checkList);
}
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("Добавить описание"),
content: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10)),
child: TextField(
decoration: new InputDecoration.collapsed(hintText: "Описание", border: InputBorder.none),
maxLines: null,
onChanged: (String value) async {
checkList.description = value;
},
),
),
actions: [
TextButton(
onPressed: () {
Navigator.pop(context);
},
child: Text("Отменить"),
),
TextButton(
onPressed: () async {
await checkList.writeToFile(checkList.toJson());
CheckListModel.checkLists.add(checkList);
Navigator.pushNamed(context, '/beforeMainCheckList', arguments: {'index': CheckListModel.checkLists.length - 1});
},
child: Text('Добавить'))
],
);
});
},
child: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10)),
width: 50,
height: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${CheckListModel.checkListModelNamesFromServer[index].name}",
style: TextStyle(fontSize: 30),
)
],
),
),
);
} else {
if (product.length != 0) {
return showPurchaseCheckLists(product, index,_subscribe);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}
},
separatorBuilder: (BuildContext context, int index) {
return Container(
height: 14,
);
});
}
Widget showPurchaseCheckLists(product, index,_subscribe) {
int getCurrentProduct() {
String? checkListModelid = CheckListModel.checkListModelNamesFromServer[index].checkListId?.toLowerCase();
int indexx = 0;
for (int i = 0; i < product.length; i++) {
if (checkListModelid == product[i].id) {
indexx = i;
}
}
return indexx;
}
return InkWell(
child: Container(
decoration: BoxDecoration(border: Border.all(color: Color.fromARGB(Desing.colorFromARGBBtn[0], Desing.colorFromARGBBtn[1], Desing.colorFromARGBBtn[2], Desing.colorFromARGBBtn[3]), width: 1), borderRadius: BorderRadius.circular(10), color: Color.fromARGB(255, 240, 240, 240)),
width: 50,
height: 80,
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
"${CheckListModel.checkListModelNamesFromServer[index].name}",
style: TextStyle(fontSize: 25),
),
Text("Купить за ${product[getCurrentProduct()].price}")
],
),
),
onTap: () {
_subscribe(product: product[getCurrentProduct()]);
},
);
}
Widget listViweBody(addCheckListMethod, serverLink, xQueryReqestForCheckListNames, product,_subscribe) {
if (CheckListModel.checkListModelNamesFromServer.length == 0) {
return FutureBuilder(
future: CheckListModel.getDataFromServer(xQueryReqestForCheckListNames, serverLink),
builder: (context, data) {
if (data.connectionState == ConnectionState.done) {
return listView(addCheckListMethod, serverLink, product,_subscribe);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
});
} else {
return listView(addCheckListMethod, serverLink, product,_subscribe);
}
}
I don't want to show Snackbar on stacked screen. When user taps on Signup from LoginScreen. Then, SignUpScreen stacked over LoginScreen. but the problem is both implements same ProviderListener and therefore, It shows Snackbar multiple times. How can I avoid it? , How can I make sure that, If current route is SignUp then show Snackbar
UserAuthService.dart
import 'package:notifications/domain/repository/firebase_repository/firebase_user_repo.dart';
import 'package:notifications/domain/services/auth_service/all_auth_builder.dart';
import 'package:notifications/export.dart';
import 'package:notifications/resources/local/local_storage.dart';
enum AuthenticationStatus {
loading,
error,
success,
}
class UserAuthService extends ChangeNotifier {
final authBuilder = AllTypeAuthBuilder();
String? _errorMsg, _sessionID;
AuthenticationStatus _status = AuthenticationStatus.loading;
EmailLinkAuthenticationRepo? _repo;
String? get errorMsg => this._errorMsg;
void get _setDefault {
_errorMsg = null;
//_sessionID = null;
}
String? get sessionID {
return LocallyStoredData.getSessionID();
}
void logOut() {
return LocallyStoredData.deleteUserKey();
}
Future<bool> userExists(String userID) async {
final isExists = await authBuilder.checkUserExists(userID);
return isExists ? true : false;
}
Future<bool> login() async {
_setDefault;
try {
await authBuilder.login();
return true;
} on BaseException catch (e) {
log("Exception $e");
_errorMsg = e.msg;
notifyListeners();
return false;
}
}
Future<bool> register(String username, String email, String password) async {
_setDefault;
try {
await authBuilder.register(username, email, password);
return true;
} on BaseException catch (e) {
log("Exception $e");
_errorMsg = e.msg;
notifyListeners();
return false;
}
}
Future<bool> signIn(String userID, String password) async {
_setDefault;
try {
await authBuilder.signIn(userID, password);
return true;
} on BaseException catch (e) {
log("Exception ${e.msg}");
_errorMsg = e.msg;
notifyListeners();
}
return false;
}
void loginWithEmail(String email) async {
_setDefault;
try {
_repo = await authBuilder.loginWithEmail(email);
_repo!.onLinkListener(
onSuccess: _onSuccess,
onError: _onError,
);
} on BaseException catch (e) {
log("Exception ${e.msg}");
_errorMsg = e.msg;
}
notifyListeners();
}
Future<bool> _onSuccess(PendingDynamicLinkData? linkData) async {
_setDefault;
try {
log("OnLinkAuthenticate");
await _repo!.onLinkAuthenticate(linkData);
return true;
} on BaseException catch (e) {
log("Error onSucess: $e");
_errorMsg = e.msg;
notifyListeners();
}
return false;
}
Future<dynamic> _onError(OnLinkErrorException? error) async {
log("Error $error in Link");
}
Future<void> tryTo(Function callback) async {
try {
await callback();
} on BaseException catch (e) {
_errorMsg = e.msg;
}
}
}
LoginScreen.dart
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
final _userIDController = TextEditingController(),
_passwordController = TextEditingController();
final _formKey = GlobalKey<FormState>();
bool? isAuthenticated;
#override
initState() {
super.initState();
}
#override
void dispose() {
_userIDController.dispose();
_passwordController.dispose();
super.dispose();
}
_onGoogleLogin() async {
context.read(loginPod).login();
}
_onLoginButtonTap() {
networkCheckCallback(context, () async {
if (_formKey.currentState!.validate()) {
WidgetUtils.showLoaderIndicator(context, 'Loading...');
final isSignedIn = await context
.read(loginPod)
.signIn(_userIDController.text, _passwordController.text);
Navigator.pop(context);
if (isSignedIn) Beamer.of(context).beamToNamed(Routes.home);
}
});
}
_resetAuthenticateState() {
if (isAuthenticated != null)
setState(() {
isAuthenticated = null;
});
}
onUsernameChange(String? value) async {
final error = await hasNetworkError();
if (_userIDController.text.isNotEmpty && error == null) {
isAuthenticated = await context.read(loginPod).userExists(value!);
setState(() {});
return;
}
_resetAuthenticateState();
}
onPasswordChange(String? value) {
//Code goes here....
}
loginWith(BuildContext context, LoginType type) {
switch (type) {
case LoginType.emailLink:
Beamer.of(context).beamToNamed(Routes.email_link_auth);
break;
case LoginType.idPassword:
Beamer.of(context).beamToNamed(Routes.login_id_pass);
break;
case LoginType.googleAuth:
Beamer.of(context).beamToNamed(Routes.login_with_google);
break;
case LoginType.unknown:
Beamer.of(context).beamToNamed(Routes.register);
break;
}
}
Future<bool> _onBackPress(_) async {
return await showDialog<bool>(
context: _,
builder: (context) {
return AlertDialog(
title: Text("Do you want to exit?"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(true),
child: Text("OK")),
TextButton(
onPressed: () {
Beamer.of(_).popRoute();
},
child: Text("Cancel"))
],
);
}) ??
false;
}
_onLoginStatus(BuildContext _, UserAuthService service) {
if (service.errorMsg != null)
_.showErrorBar(
content: Text(
"WithLogin" + service.errorMsg!,
style: TextStyle(fontSize: 12.sp),
));
}
#override
Widget build(BuildContext _) {
return ProviderListener(
onChange: _onLoginStatus,
provider: loginPod,
child: WillPopScope(
onWillPop: () => _onBackPress(_),
child: Scaffold(
body: SingleChildScrollView(
child: SizedBox(height: 1.sh, child: _buildLoginScreen())),
),
),
);
}
Widget _buildLoginScreen() {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
//_buildVrtSpacer(60),
_buildHeading(),
//_buildVrtSpacer(30),
_buildForm(),
//_buildVrtSpacer(30),
_buildIconButtons(),
_buildSignUpButton(),
],
);
}
BoldHeadingWidget _buildHeading() =>
BoldHeadingWidget(heading: AppStrings.login);
ResponsiveVrtSpacer _buildVrtSpacer(double value) =>
ResponsiveVrtSpacer(space: value);
Widget _buildForm() {
return CustomForm(
formKey: _formKey,
child: Column(
children: [
_buildUsernameField(),
_buildVrtSpacer(10),
_buildPasswordField(),
_buildForgetPassword(),
_buildLoginButton(),
],
),
);
}
Widget _buildPasswordField() {
return CustomTextFieldWithLabeled(
controller: _passwordController,
label: AppStrings.password,
hintText: AppStrings.password,
onValidate: (String? value) =>
(value!.isEmpty) ? AppStrings.emptyPasswordMsg : null,
obscureText: true,
onChange: onPasswordChange,
icon: CupertinoIcons.lock);
}
Widget _buildUsernameField() {
return CustomTextFieldWithLabeled(
controller: _userIDController,
label: AppStrings.usernameOrEmail,
hintText: AppStrings.usernameOrEmail1,
icon: CupertinoIcons.person,
onChange: onUsernameChange,
onValidate: (String? value) =>
(value!.isEmpty) ? AppStrings.emptyUserIDMsg : null,
suffixIcon: isAuthenticated == null
? null
: (isAuthenticated!
? CupertinoIcons.checkmark_alt_circle_fill
: CupertinoIcons.clear_circled_solid),
suffixColor: isAuthenticated == null
? null
: (isAuthenticated! ? Colors.green : Styles.defaultColor));
}
Widget _buildIconButtons() {
return Column(
children: [
Text("Or", style: TextStyle(fontSize: 14.sp)),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const SizedBox(width: 10),
_buildIconButton(
iconPath: 'assets/icons/email-icon.svg', onTap: () {}),
const SizedBox(width: 8),
_buildIconButton(
iconPath: 'assets/icons/icons8-google.svg',
onTap: _onGoogleLogin),
],
),
],
);
}
Widget _buildIconButton(
{required String iconPath, required VoidCallback onTap}) {
return GestureDetector(
onTap: onTap,
child: SvgPicture.asset(
iconPath,
height: 30.sp,
width: 30.sp,
));
}
Widget _buildSignUpButton() {
return Column(
mainAxisAlignment: MainAxisAlignment.end,
children: [
Text(
AppStrings.dontHaveAccount,
style: TextStyle(color: Colors.black54, fontSize: 14.sp),
),
const SizedBox(height: 5),
CustomTextButton(
title: "Sign Up",
onPressed: () => Beamer.of(context).beamToNamed(Routes.register)),
],
);
}
Widget _buildLoginButton() {
return DefaultElevatedButton(
onPressed: _onLoginButtonTap,
title: AppStrings.login,
);
}
Widget _buildForgetPassword() {
return Align(
alignment: Alignment.centerRight,
child: TextButton(
style: ButtonStyle(
overlayColor: MaterialStateProperty.all(Color(0x11000000)),
foregroundColor: MaterialStateProperty.all(Color(0x55000000)),
),
onPressed: () {},
child: Text("Forget Password?"),
),
);
}
}
SignUpScreen.dart
class SignUp extends StatefulWidget {
const SignUp({Key? key}) : super(key: key);
#override
_SignUpState createState() => _SignUpState();
}
class _SignUpState extends State<SignUp> {
final _formKey = GlobalKey<FormState>();
final _usernameController = TextEditingController(text: "hammad11"),
_emailController = TextEditingController(text: "mason#gmail.com"),
_passwordController = TextEditingController(text: "ha11"),
_confirmPassController = TextEditingController(text: "ha11");
_onValidate(String? value, ValidationType type) {
switch (type) {
case ValidationType.username:
if (value!.isNotEmpty && value.length < 8)
return "Username Must Be 8 characters long";
else if (value.isEmpty) return "Username required";
return null;
case ValidationType.email:
if (value!.isEmpty)
return "Email required";
else if (!value.isEmail) return "Please enter a Valid Email";
return null;
case ValidationType.password:
if (value!.isEmpty)
return "Password required";
else if (value.isAlphabetOnly || value.isNumericOnly)
return "Password must be AlphaNumeric";
return null;
case ValidationType.confirmPassword:
if (value!.isEmpty)
return "Confirm Password required";
else if (value != _passwordController.text)
return "Password doesn't match";
return null;
}
}
_onRegister() async {
//Clears any snackbar opened due to Error or Multiple clicks
//ScaffoldMessenger.of(context).clearSnackBars();
log("SignUp -> _onRegisterTap ");
if (_formKey.currentState!.validate()) {
WidgetUtils.showLoaderIndicator(context, "Please wait! Loading.....");
final isLoggedIn = await context.read(loginPod).register(
_usernameController.text,
_emailController.text,
_passwordController.text,
);
await Beamer.of(context).popRoute();
if (isLoggedIn) Beamer.of(context).beamToNamed(Routes.login);
} else
log("Form Input Invalid");
}
_onChanged(_, UserAuthService service) async {
if (service.errorMsg != null) WidgetUtils.snackBar(_, service.errorMsg!);
// if (!service.isLoading) await Beamer.of(context).popRoute();
// if (service.taskCompleted) {
// log("User Added Successfully");
// Beamer.of(context).popToNamed(
// Routes.login_id_pass,
// replaceCurrent: true,
// );
// } else {
// WidgetUtils.snackBar(context, service.errorMsg!);
// }
}
_alreadyHaveAccount() => Beamer.of(context).popToNamed(Routes.main);
#override
Widget build(BuildContext context) {
return ProviderListener(
provider: loginPod,
onChange: _onChanged,
child: Scaffold(
body: LayoutBuilder(builder: (context, constraints) {
return SingleChildScrollView(
child: SizedBox(
height: 1.sh,
child: Column(
children: [
_buildSpacer(50),
BoldHeadingWidget(heading: "Sign Up"),
CustomForm(
child: Column(
children: [
CustomTextFieldWithLabeled(
label: "Username",
hintText: "Type Username",
onValidate: (value) =>
_onValidate(value, ValidationType.username),
controller: _usernameController,
icon: CupertinoIcons.person),
CustomTextFieldWithLabeled(
label: "Email",
hintText: "Type Email",
controller: _emailController,
onValidate: (value) =>
_onValidate(value, ValidationType.email),
icon: CupertinoIcons.envelope),
CustomTextFieldWithLabeled(
label: "Password",
hintText: "Type Password",
controller: _passwordController,
onValidate: (value) =>
_onValidate(value, ValidationType.password),
icon: CupertinoIcons.lock),
CustomTextFieldWithLabeled(
label: "Confirm Password",
hintText: "Type Confirm Password",
onValidate: (value) => _onValidate(
value, ValidationType.confirmPassword),
controller: _confirmPassController,
icon: CupertinoIcons.lock),
Row(
children: [
Checkbox(
value: true,
onChanged: (value) {},
materialTapTargetSize:
MaterialTapTargetSize.shrinkWrap,
),
Flexible(
child: Text(
"I accept Terms & Conditions and the Privacy Policy",
style: TextStyle(fontSize: 13.sp),
),
),
],
),
_buildSpacer(10),
DefaultElevatedButton(
title: "Sign Up",
onPressed: _onRegister,
),
],
),
formKey: _formKey,
),
const SizedBox(height: 10),
CustomTextButton(
onPressed: () => Beamer.of(context)
.popToNamed(Routes.login, stacked: false),
title: AppStrings.alreadyHaveAccount,
),
// Spacer(flex: 2),
],
),
),
);
}),
),
);
}
ResponsiveVrtSpacer _buildSpacer(double value) =>
ResponsiveVrtSpacer(space: value.h);
}
showsnackbar(String message, context) {
final snackbar = SnackBar(
content: Text(message),
duration: Duration(seconds: 2),
);
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(snackbar);
}
Use this function to call snackbar it will remove the current snackbar and will only show the latest.
I am new to flutter, I have successfully retrieved the user email and username from Azure AD Auth. But I want to display this in Text widget in home screen. How can I send the username from the Signin class to home screen and display the name?
Signin
class _SignInState extends State<SignIn> {
MsalMobile msal;
bool isSignedIn = false;
#override
void initState() {
super.initState();
MsalMobile.create('assets/auth_config.json', authority).then((client) {
setState(() {
msal = client;
});
refreshSignedInStatus();
});
}
/// Updates the signed in state
refreshSignedInStatus() {
msal.getSignedIn().then((loggedIn) {
print('refreshing');
setState(() {
isSignedIn = loggedIn;
if(isSignedIn) {
// Your navigation code
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => NavScreen()));
}
});
});
}
/// Signs a user in
handleSignIn() async {
await msal.signIn(null, [SCOPE]).then((result) {
refreshSignedInStatus();
}).catchError((exception) {
if (exception is MsalMobileException) {
logMsalMobileError(exception);
} else {
final ex = exception as Exception;
print('exception occurred');
print(ex.toString());
}
}
);
}
logMsalMobileError(MsalMobileException exception) {
print('${exception.errorCode}: ${exception.message}');
if (exception.innerException != null) {
print(
'inner exception = ${exception.innerException.errorCode}: ${exception.innerException.message}');
}
}
/// Signs a user out.
handleSignOut() async {
try {
print('signing out');
await msal.signOut();
print('signout done');
refreshSignedInStatus();
} on MsalMobileException catch (exception) {
logMsalMobileError(exception);
}
}
/// Gets the current and prior accounts.
handleGetAccount() async {
await msal.getAccount().then((result) {
if (result.currentAccount != null) {
return result.currentAccount.username;
} else {
print('no account found');
}
}).catchError((exception) {
if (exception is MsalMobileException) {
logMsalMobileError(exception);
} else {
print('exception occurred');
}
});
}
home screen
class _HomeScreenDesktop extends StatelessWidget {
final SignIn handleGetAccount;
final TrackingScrollController scrollController;
const _HomeScreenDesktop({Key key, this.scrollController, this.handleGetAccount}) : super(key: key);
#override
Widget build(BuildContext context) {
return Row(
children: [
Flexible(
flex: 2,
child: Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: const EdgeInsets.all(12.0),
child: MoreOptionsList(currentUser: currentUser),
),
),
),
const Spacer(),
Container(
height: 1000.0,
width: 600.0,
child: ListView(
controller: scrollController,
children: <Widget>[
SizedBox(
height: 30,
),
Padding(
padding: EdgeInsets.only(left: 16, right: 16),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
"Show Retrieved username from azure account",
style: GoogleFonts.openSans(
textStyle: TextStyle(
color: Colors.white,
fontSize: 18,
fontWeight: FontWeight.bold)),
),
SizedBox(
height: 4,
),
],
),
IconButton(
alignment: Alignment.topRight,
icon: Image.asset(
"assets/notification.png",
width: 24,
),
onPressed: () => {},
),
],
),
),
SizedBox(
height: 20,
),
GridDashboardwe()
],
)),
const Spacer(),
Flexible(
flex: 2,
child: Container(
color: Color(0xff392850) ,
),
)
],
);
}
}
I am using this method on the signin screen to get the account details
/// Gets the current and prior accounts.
handleGetAccount() async {
await msal.getAccount().then((result) {
if (result.currentAccount != null) {
return result.currentAccount.username;
} else {
print('no account found');
}
}).catchError((exception) {
if (exception is MsalMobileException) {
logMsalMobileError(exception);
} else {
print('exception occurred');
}
});
}
You need to pass the user object via parameters from the SignIn class to the _HomeScreenDesktop widget. Also, you should make the home screen widget public instead of making it private. Using an underscore before the class name means it is private ie, use HomeScreenDesktop instead of _HomeScreenDesktop.
Also please specify the return type of methods which will be really very helpful.
Updated handleGetAccount method:
Future<dynamic> handleGetAccount() async { // <-- Replace dynamic with type of currentAccount
await msal.getAccount().then((result) {
if (result.currentAccount != null) {
return result.currentAccount;
} else {
print('no account found');
return null;
}
}).catchError((exception) {
if (exception is MsalMobileException) {
logMsalMobileError(exception);
} else {
print('exception occurred');
return null;
}
});
Call method handleGetAccount to get current signed-in user & then you can pass this user object to the HomeScreenWidget, which will look something like this:
/// Updates the signed in state
refreshSignedInStatus() {
msal.getSignedIn().then((loggedIn) {
print('refreshing');
setState(() {
isSignedIn = loggedIn;
if(isSignedIn) {
dynamic currentAccount = await handleGetAccount();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => NavScreen(
currentAccount: currentAccount, // <--- Passing required named parameter "currentAccount" in NavScreen widget.
),
),
);
}
});
});
}
The NavScreen widget will also be updated by creating a constructor as follows:
NavScreen({
#required this.currentAccount, // Required currentUser parameter
});
final dynamic currentAccount; // Replace dynamic with the type of currentUser
From NavScreen, I guess you must be navigating to HomeScreenDesktop somehow, but while doing this, you need to pass this currentAccount object to the HomeScreenDesktop & create the same constructor & parameter as in NavScreen widget (something like this):
HomeScreenDesktop({
#required this.currentAccount, // Required currentUser parameter
});
final dynamic currentAccount; // Replace dynamic with the type of currentUser
You will also have to pass the currentAccount object while navigating.
It will look something like this:
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => HomeScreenDesktop(
currentAccount: currentAccount,
),
),
);