I want to add showSnackBar to feedback user for this case - flutter

This is my onSavedChange func for user Information updating. also where do i need to add toast/snackbar, i cannot figure it out please help.
void onSavedChange() async {
if (!_formKey.currentState!.validate()) {
return;
} else {
try {
loadingController.add(true);
final response = await userApi.changeUserInformation(
newEmail: textEditingControllerEmail.text.trim(),
newFirstName: textEditingControllerFirstName.text.trim(),
newLastName: textEditingControllerLastName.text.trim(),
);
printMe("response:$response");
await LocalStorage.saveUserData(firstName: response.firstName, lastName: response.lastName, email: response.email );
AppConstant.FIRST_NAME = await LocalStorage.read(key: LocalStorage.FIRSTNAME);
AppConstant.LAST_NAME = await LocalStorage.read(key: LocalStorage.LAST_NAME);
AppConstant.USER_EMAIL = await LocalStorage.read(key: LocalStorage.UER_EMAIL_KEY);
// printMe("data1 is ${AppConstant.FIRST_NAME}");
// printMe("data2 is ${AppConstant.LAST_NAME}");
// printMe("data3 is ${ AppConstant.USER_EMAIL}");
bool? isLoading = await Navigator.push(context, MaterialPageRoute(builder: (context) => const HomePage(startPageIndex: 4, )));
if (isLoading != null) {
loadingController.add(isLoading);
}
}
catch (error) {
isLoading.add(false);
UIHelper.showErrorMessageDialog(error, context);
}
}
}

Related

Difference between Buildcontext and NavigatorKey.currentState.context

I'm currently using Provider as state management and also to keep all my function in it. At first i was using a callback method for me to to navigate thru screen when function in my Provider class succeed.
Future login(String email, String password, Function callback) async {
_isLoading = true;
notifyListeners();
bool isSuccess = false;
try {
final ApiResponse apiResponse = await authRepo!.login(email, password);
if (apiResponse.response != null && apiResponse.response!.statusCode == 200) {
isSuccess = true;
callback(isSuccess, apiResponse.response!.data[Constants.responseMsg]);
} else {
callback(isSuccess, apiResponse.error);
}
} catch (e) {
_isLoading = false;
print('login error: $e');
notifyListeners();
rethrow;
}
_isLoading = false;
notifyListeners();
}
but then i realized i could just pass the Buildcontext and navigating inside the function itself without using a callback method.
Future login(String email, String password, BuildContext context) async {
_isLoading = true;
notifyListeners();
try {
final ApiResponse apiResponse = await authRepo!.login(email, password);
if (apiResponse.response != null && apiResponse.response!.statusCode == 200) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) => DashboardScreen(),
settings: RouteSettings(name: '/Dashboard'),
),
);
} else {
GlobalFunction.showToast(apiResponse.error);
}
} catch (e) {
_isLoading = false;
print('login error: $e');
notifyListeners();
rethrow;
}
_isLoading = false;
notifyListeners();
}
and then i also realize i could use NavigatorKey.currentState!.context to navigate so i dont need the pass the Buildcontext.
Future login(String email, String password) async {
_isLoading = true;
notifyListeners();
try {
final ApiResponse apiResponse = await authRepo!.login(email, password);
if (apiResponse.response != null && apiResponse.response!.statusCode == 200) {
BuildContext _context = navigatorKey.currentState!.context;
Navigator.of(_context).pushReplacement(
MaterialPageRoute(
builder: (BuildContext context) => DashboardScreen(),
settings: RouteSettings(name: '/Dashboard'),
),
);
} else {
GlobalFunction.showToast(apiResponse.error);
}
} catch (e) {
_isLoading = false;
print('login error: $e');
notifyListeners();
rethrow;
}
_isLoading = false;
notifyListeners();
}
i wonder which one is the better way?

Flutter call function from other class?

Hi I am building flutter function which able to capture qr code and after display the result after user scan QR Code. I need user to be able navigate to previous scanning screen by using button if they need rescan or scan new qr code. This is code for button which on Scanview class.
SizedBox(height: 40,),
CupertinoButton(
color: Color(0xFF88070B),
child:Text("Re-scan QR "),
onPressed: (){
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => **ScanFunction**(),
),
);
}
)
I want to call this _scanQR method from other class ScanFunction . What is proper way when user tap Re-scan QR button and call _scanQR method which is on other class? How to access method from other class? Thanks for help.
class ScanFunction extends StatefulWidget {
#override
ScanFunctionState createState() {
return ScanFunctionState();
}
}
class ScanFunctionState extends State<ScanFunction> {
String result = "Maklumat Inventori";
Future _scanQR() async {
try {
String qrResult = await BarcodeScanner.scan();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Scanresultview(qrResult),
),
);
} on PlatformException catch (ex) {
if (ex.code == BarcodeScanner.CameraAccessDenied) {
setState(() {
result = "Kebenaran kamera telah ditolak";
});
} else {
setState(() {
result = "Ralat tidak diketahui$ex";
});
}
} on FormatException {
setState(() {
result = "Anda menekan butang belakang sebelum mengimbas apa-apa";
});
} catch (ex) {
setState(() {
result = "Ralat tidak diketahui $ex";
});
}
}
You can call this method from initState.
class ScanFunction extends StatefulWidget {
#override
ScanFunctionState createState() {
return ScanFunctionState();
}
}
class ScanFunctionState extends State<ScanFunction> {
#override
void initState() {
_scanQR();
super.initState();
}
String result = "Maklumat Inventori";
Future _scanQR() async {
try {
String qrResult = await BarcodeScanner.scan();
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => Scanresultview(qrResult),
),
);
} on PlatformException catch (ex) {
if (ex.code == BarcodeScanner.CameraAccessDenied) {
setState(() {
result = "Kebenaran kamera telah ditolak";
});
} else {
setState(() {
result = "Ralat tidak diketahui$ex";
});
}
} on FormatException {
setState(() {
result = "Anda menekan butang belakang sebelum mengimbas apa-apa";
});
} catch (ex) {
setState(() {
result = "Ralat tidak diketahui $ex";
});
}
}

NoSuchMethodError: Class 'FlutterError' has no instance getter 'code'. Receiver: Instance of 'FlutterError' Tried calling: code)

I've been trying a sample Flutter application code from GitHub to simply login and register the user on Firebase. Every time I login or register after clean building the application, it takes me to the main page but throws this exception Exception has occurred. NoSuchMethodError (NoSuchMethodError: Class 'FlutterError' has no instance getter 'code'. Receiver: Instance of 'FlutterError' Tried calling: code)
I've no idea what 'FlutterError' is referring to because I don't see any such class. and there are two occurrences of code in the file named 'login-register.dart'. I'm attaching the code below:
(Note: it runs okay after I hot reload the app and the user is already logged in, only throws exception the first time)
void _validateLoginInput() async {
final FormState form = _formKey.currentState;
if (_formKey.currentState.validate()) {
form.save();
_sheetController.setState(() {
_loading = true;
});
try {
final FirebaseUser user = (await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password)).user;
// final uid = user.uid;
Navigator.of(context).pushReplacementNamed('/home');
} catch (error) {
switch (error.code) {
case "ERROR_USER_NOT_FOUND":
{
_sheetController.setState(() {
errorMsg =
"There is no user with such entries. Please try again.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
case "ERROR_WRONG_PASSWORD":
{
_sheetController.setState(() {
errorMsg = "Password doesn\'t match your email.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
default:
{
_sheetController.setState(() {
errorMsg = "";
});
}
}
}
} else {
setState(() {
_autoValidate = true;
});
}
}
void _validateRegisterInput() async {
final FormState form = _formKey.currentState;
if (_formKey.currentState.validate()) {
form.save();
_sheetController.setState(() {
_loading = true;
});
try {
final FirebaseUser user = (await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: _email, password: _password)).user;
UserUpdateInfo userUpdateInfo = new UserUpdateInfo();
userUpdateInfo.displayName = _displayName;
user.updateProfile(userUpdateInfo).then((onValue) {
Navigator.of(context).pushReplacementNamed('/home');
Firestore.instance.collection('users').document().setData(
{'email': _email, 'displayName': _displayName}).then((onValue) {
_sheetController.setState(() {
_loading = false;
});
});
});
} catch (error) {
switch (error.code) {
case "ERROR_EMAIL_ALREADY_IN_USE":
{
_sheetController.setState(() {
errorMsg = "This email is already in use.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
case "ERROR_WEAK_PASSWORD":
{
_sheetController.setState(() {
errorMsg = "The password must be 6 characters long or more.";
_loading = false;
});
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
content: Container(
child: Text(errorMsg),
),
);
});
}
break;
default:
{
_sheetController.setState(() {
errorMsg = "";
});
}
}
}
} else {
setState(() {
_autoValidate = true;
});
}
}
The exception you're catching doesn't have a code property. That only exists with the firebase exception implementation, not the general exception class.
If you expect a certain type of error, you should explicitly catch that error and handle it properly and have a separate catch block for all other errors.
This can be done with an on ... catch block:
try {
final FirebaseUser user = (await FirebaseAuth.instance
.signInWithEmailAndPassword(email: _email, password: _password)).user;
// final uid = user.uid;
Navigator.of(context).pushReplacementNamed('/home');
} on FirebaseAuthException catch (error) {
...
} catch(e) {
...
}
The methods you're calling in the code you shared will throw FirebaseAuthExceptions as shown in the code above.
You are getting an error that is not a FirebaseError but a FlutterError. This means, it does not implement a code field.
You can simply put
if(!(error is FirebaseError)){
print(error.message); // this is the actual error that you are getting
}
right below catch(error) { (in both files) to handle this.
However, it seems like you get another Flutter Error that you might want to handle. It should be printed to the console now.

Flutter WebRTC audio but no video

So I'm building a video calling application using flutter, flutterWeb, and the WebRTC package.
I have a spring boot server sitting in the middle to pass the messages between the two clients.
Each side shows the local video, but neither shows the remote. Audio does work though. I got som nasty feedback loops. Testing with headphones showed that audio does indeed work.
My singaling code
typedef void StreamStateCallback(MediaStream stream);
class CallingService {
String sendToUserId;
String currentUserId;
final String authToken;
final StompClient _client;
final StreamStateCallback onAddRemoteStream;
final StreamStateCallback onRemoveRemoteStream;
final StreamStateCallback onAddLocalStream;
RTCPeerConnection _peerConnection;
List<RTCIceCandidate> _remoteCandidates = [];
String destination;
var hasOffer = false;
var isNegotiating = false;
MediaStream _localStream;
final Map<String, dynamic> _constraints = {
'mandatory': {
'OfferToReceiveAudio': true,
'OfferToReceiveVideo': true,
},
'optional': [],
};
CallingService(
this._client,
this.sendToUserId,
this.currentUserId,
this.authToken,
this.onAddRemoteStream,
this.onRemoveRemoteStream,
this.onAddLocalStream) {
destination = '/app/start-call/$sendToUserId';
print("destination $destination");
_client.subscribe(
destination: destination,
headers: {'Authorization': "$authToken"},
callback: (StompFrame frame) => processMessage(jsonDecode(frame.body)));
}
Future<void> startCall() async {
await processRemoteStream();
RTCSessionDescription description =
await _peerConnection.createOffer(_constraints);
await _peerConnection.setLocalDescription(description);
var message = RtcMessage(RtcMessageType.OFFER, currentUserId, {
'description': {'sdp': description.sdp, 'type': description.type},
});
sendMessage(message);
}
Future<void> processMessage(Map<String, dynamic> messageJson) async {
var message = RtcMessage.fromJson(messageJson);
if (message.from == currentUserId) {
return;
}
print("processing ${message.messageType.toString()}");
switch (message.messageType) {
case RtcMessageType.BYE:
// TODO: Handle this case.
break;
case RtcMessageType.LEAVE:
// TODO: Handle this case.
break;
case RtcMessageType.CANDIDATE:
await processCandidate(message);
break;
case RtcMessageType.ANSWER:
await processAnswer(message);
break;
case RtcMessageType.OFFER:
await processOffer(message);
break;
}
}
Future<void> processCandidate(RtcMessage candidate) async {
Map<String, dynamic> map = candidate.data['candidate'];
var rtcCandidate = RTCIceCandidate(
map['candidate'],
map['sdpMid'],
map['sdpMLineIndex'],
);
if (_peerConnection != null) {
_peerConnection.addCandidate(rtcCandidate);
} else {
_remoteCandidates.add(rtcCandidate);
}
}
Future<void> processAnswer(RtcMessage answer) async {
if (isNegotiating) {
return;
}
isNegotiating = true;
var description = answer.data['description'];
if (_peerConnection == null) {
return;
}
await _peerConnection.setRemoteDescription(
RTCSessionDescription(description['sdp'], description['type']));
}
Future<void> processOffer(RtcMessage offer) async {
await processRemoteStream();
var description = offer.data['description'];
await _peerConnection.setRemoteDescription(
new RTCSessionDescription(description['sdp'], description['type']));
var answerDescription = await _peerConnection.createAnswer(_constraints);
await _peerConnection.setLocalDescription(answerDescription);
var answerMessage = RtcMessage(RtcMessageType.ANSWER, currentUserId, {
'description': {
'sdp': answerDescription.sdp,
'type': answerDescription.type
},
});
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
}
Future<void> processRemoteStream() async {
_localStream = await createStream();
_peerConnection = await createPeerConnection(_iceServers, _config);
_peerConnection.addStream(_localStream);
_peerConnection.onSignalingState = (state) {
//isNegotiating = state != RTCSignalingState.RTCSignalingStateStable;
};
_peerConnection.onAddStream = (MediaStream stream) {
this.onAddRemoteStream(stream);
};
_peerConnection.onRemoveStream =
(MediaStream stream) => this.onRemoveRemoteStream(stream);
_peerConnection.onIceCandidate = (RTCIceCandidate candidate) {
var data = {
'candidate': {
'sdpMLineIndex': candidate.sdpMlineIndex,
'sdpMid': candidate.sdpMid,
'candidate': candidate.candidate,
},
};
var message = RtcMessage(RtcMessageType.CANDIDATE, currentUserId, data);
sendMessage(message);
};
}
void sendMessage(RtcMessage message) {
_client.send(
destination: destination,
headers: {'Authorization': "$authToken"},
body: jsonEncode(message.toJson()));
}
Map<String, dynamic> _iceServers = {
'iceServers': [
{'urls': 'stun:stun.l.google.com:19302'},
/*
* turn server configuration example.
{
'url': 'turn:123.45.67.89:3478',
'username': 'change_to_real_user',
'credential': 'change_to_real_secret'
},
*/
]
};
final Map<String, dynamic> _config = {
'mandatory': {},
'optional': [
{'DtlsSrtpKeyAgreement': true},
],
};
Future<MediaStream> createStream() async {
final Map<String, dynamic> mediaConstraints = {
'audio': true,
'video': {
'mandatory': {
'minWidth': '640',
'minHeight': '480',
'minFrameRate': '30',
},
'facingMode': 'user',
'optional': [],
}
};
MediaStream stream = await navigator.getUserMedia(mediaConstraints);
if (this.onAddLocalStream != null) {
this.onAddLocalStream(stream);
}
return stream;
}
}
Here are my widgets
class _CallScreenState extends State<CallScreen> {
StompClient _client;
CallingService _callingService;
RTCVideoRenderer _localRenderer = new RTCVideoRenderer();
RTCVideoRenderer _remoteRenderer = new RTCVideoRenderer();
final UserService userService = GetIt.instance.get<UserService>();
void onConnectCallback(StompClient client, StompFrame connectFrame) async {
var currentUser = await userService.getCurrentUser();
_callingService = CallingService(
_client,
widget.intent.toUserId.toString(),
currentUser.id.toString(),
widget.intent.authToken,
onAddRemoteStream,
onRemoveRemoteStream,
onAddLocalStream);
if (widget.intent.initialMessage != null) {
_callingService.processMessage(jsonDecode(widget.intent.initialMessage));
} else {
_callingService.startCall();
}
}
void onAddRemoteStream(MediaStream stream) {
_remoteRenderer.srcObject = stream;
}
void onRemoveRemoteStream(MediaStream steam) {
_remoteRenderer.srcObject = null;
}
void onAddLocalStream(MediaStream stream) {
_localRenderer.srcObject = stream;
}
#override
void initState() {
super.initState();
_localRenderer.initialize();
_remoteRenderer.initialize();
_client = StompClient(
config: StompConfig(
url: 'ws://${DomainService.getDomainBase()}/stomp',
onConnect: onConnectCallback,
onWebSocketError: (dynamic error) => print(error.toString()),
stompConnectHeaders: {'Authorization': "${widget.intent.authToken}"},
onDisconnect: (message) => print("disconnected ${message.body}"),),
);
_client.activate();
}
#override
Widget build(BuildContext context) {
return PlatformScaffold(
pageTitle: "",
child: Flex(
direction: Axis.vertical,
children: [
Flexible(
flex: 1,
child: RTCVideoView(_localRenderer),
),
Flexible(
flex: 1,
child: RTCVideoView(_remoteRenderer),
)
],
),
);
}
}
I put a print statment in the the widget on the addRemoteStream callback, and it's getting called. So some kind of stream is being sent. I'm just not sure why the video isnt' showing.
So my problem was that I wasn't adding queued candidates to the caller.
I added
sendMessage(answerMessage);
if (_remoteCandidates.isNotEmpty) {
_remoteCandidates
.forEach((candidate) => _peerConnection.addCandidate(candidate));
_remoteCandidates.clear();
}
to the processAnswer method and it works just fine!

Show dialog using Scoped model

I have a basic login form, with my LoginModel.
But I do not understand how I can call to the function notifyListeners to display a dialog in my view.
The login widget:
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new ScopedModel<LoginModel>(
model: _loginModel,
child: Center(child: ScopedModelDescendant<LoginModel>(
builder: (context, child, model) {
if (model.status == Status.LOADING) {
return Loading();
}
else return showForm(context);
}))));
}
And the login model:
class LoginModel extends Model {
Status _status = Status.READY;
Status get status => _status;
void onLogin(String username, String password) async {
_status = Status.LOADING;
notifyListeners();
try {
await api.login();
_status = Status.SUCCESS;
notifyListeners();
} catch (response) {
_status = Status.ERROR;
notifyListeners();
}
}
I need to display a dialog when the status is Error
Finally I got this, just returning a Future in the method onLogin
Future<bool> onLogin(String username, String password) async {
_status = Status.LOADING;
notifyListeners();
try {
await api.login();
_status = Status.SUCCESS;
notifyListeners();
return true;
} catch (response) {
_status = Status.ERROR;
notifyListeners();
return false;
}
}
And in the widget:
onPressed: () async {
bool success = await _loginModel.onLogin(_usernameController.text, _passwordController.text);
if(success) {
Navigator.pop(context, true);
}
else{
_showDialogError();
}
}