Flutter read shared preferences in main then decide which startup page? - async-await

I want to judge which page to start up in main (actually is login page and the home page). So I have to read isLogin in preferences. How to do that in main?
I tied these codes:
Future<Null> checkIsLogin() async {
String _token = "";
// If token exist already, then HomePage
SharedPreferences prefs = await SharedPreferences.getInstance();
_token = prefs.getString("token");
print('get token from prefs: ' + _token);
if (_token != "" && _token != null) {
// already login
print("alreay login.");
isLogin = true;
}
}
void main() {
App.init();
// if we have token then go to HomePage directly otherwise go to LoginPage.
Widget _defaultHome = new LoginPage();
checkIsLogin();
if (isLogin) {
_defaultHome = new HomePage();
}
runApp(new MaterialApp(
debugShowCheckedModeBanner: false,
theme: globalThemeData,
home: _defaultHome
));
}
above code, isLogin is an global variable. There was an error:
Performing full restart...
Restarted app in 2,810ms.
[VERBOSE-2:dart_error.cc(16)] Unhandled exception:
Invalid argument(s)
#0 _StringBase.+ (dart:core/runtime/libstring_patch.dart:245:57)
#1 checkIsLogin (file:///Volumes/xs/awesome/uranus/clients/flutter/flutter_asgard/lib/main.dart:17:34)
<asynchronous suspension>
#2 main (file:///Volumes/xs/awesome/uranus/clients/flutter/flutter_asgard/lib/main.dart:29:3)
#3 _startIsolate.<anonymous closure> (dart:isolate/runtime/libisolate_patch.dart:279:19)
#4 _RawReceivePortImpl._handleMessage (dart:isolate/runtime/libisolate_patch.dart:165:12)
Seems there are issue to call async in main, how to get it to work?

This is what i did,
void main() {
WidgetsFlutterBinding.ensureInitialized();
SharedPreferences.getInstance().then((instance) {
StorageService().sharedPreferencesInstance = instance; // Storage service is a service to manage all shared preferences stuff. I keep the instance there and access it whenever i wanted.
runApp(MyApp());
});
}
Then in the Material App Build
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'App Title',
home: _checkUserLoggedIn()
? HomeScreen()
: LoginPage(),
);
}
_checkUserLoggedIn Function
bool _checkUserLoggedIn() {
return _storageService.getFromShared('isLoggedIn'); // Just a get method from shared preferences
}

You need await checkIsLogin.
This is my code:
Future<Null> main() async {
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
]);
Screen.keepOn(true);
SharedService.sharedPreferences = await SharedPreferences.getInstance();
account = SharedService.sharedPreferences.getString("Account");
password = SharedService.sharedPreferences.getString("Password");
runApp(new MyApp());
}

Load the Homepage and if the user is not logged in, then replace it with your LoginPage()
#override
void initState() {
super.initState();
checkIsLogin();
}
Future<Null> checkIsLogin() async {
String _token = "";
SharedPreferences prefs = await SharedPreferences.getInstance();
_token = prefs.getString("token");
if (_token != "" && _token != null) {
print("alreay login.");
//your home page is loaded
}
else
{
//replace it with the login page
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => new LoginPage()),
);
}
}

Create a SplashPage that you can pass as a home route in your MaterialApp()
Inside SplashPage, for example initState() you can check for login and than push new route to a Navigator.
SplashPage can just be centered logo, with optional animation.

Here is what you can do
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() async {
String initialRoute;
// handle exceptions caused by making main async
WidgetsFlutterBinding.ensureInitialized();
// init a shared preferences variable
SharedPreferences prefs = await SharedPreferences.getInstance();
// read token
String token = prefs.getString('token');
// use dart's null safety operater
if (token?.isEmpty ?? true)
initialRoute = 'login';
else
initialRoute = '/';
// create a flutter material app as usual
Widget app = MaterialApp(
...
initialRoute: initialRoute,
);
// mount and run the flutter app
runApp(app);
}
For more details, you can refer to this article: https://www.ravsam.in/blog/dynamic-home-route-in-flutter-app/

Related

In Flutter how can I receive an image in a main screen and then redirect it to another screen?

I'm developing a Card Game where the user can add new cards. Each card has an image path.
I'm trying to prefill my New Card screen with an image that comes from another app (ex: Gallery).
For this I'm using package receive_sharing_intent, having successfully managed to print to console my image path within my app. Now what I can't do is to redirect to new screen after getting my image.
Here is the relevant code from my home screen:
class Menu extends StatefulWidget {
final String title;
Menu({Key key, this.title}) : super(key: key);
#override
_MenuState createState() => _MenuState();
}
class _MenuState extends State<Menu> {
StreamSubscription _intentDataStreamSubscription;
List<SharedMediaFile> _sharedFiles;
#override
void initState() {
super.initState();
bootstrapCards();
getOurCards();
// For sharing images coming from outside the app while the app is in the memory
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream()
.listen((List<SharedMediaFile> value) {
setState(() {
_sharedFiles = value;
print("Shared:" + (_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
_goCardAdd((_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
});
}, onError: (err) {
print("getIntentDataStream error: $err");
});
// For sharing images coming from outside the app while the app is closed
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
setState(() {
_sharedFiles = value;
print("Shared:" + (_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
_goCardAdd((_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
});
});
}
#override
void dispose() {
_intentDataStreamSubscription.cancel();
super.dispose();
}
void _goCardAdd(path) {
MyCard myEmptyCard = new MyCard();
myEmptyCard.url=path;
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) => AddEditScreen(myCard: myEmptyCard)),
);
}
It seems that the _goCardAdd call isn't doing anything and I cannot understand why.
And if I put it after the setState call then I get an error saying
setState() or markNeedsBuild() called during build.
The complete code can be found at https://github.com/diogocsc/papoapapoFlutter
It seems it might have been some kind of build issue, as i have only made minor changes to the code and it started working. Meanwhile I needed to add an if clause to check if the file is there, because if not, it would always redirect me to the new card screen when I opened my app, even if no image file was underway. Check the code below.
class _MenuState extends State<Menu> {
StreamSubscription _intentDataStreamSubscription;
List<SharedMediaFile> _sharedFiles;
#override
void initState() {
super.initState();
bootstrapCards();
// For sharing images coming from outside the app while the app is in the memory
_intentDataStreamSubscription = ReceiveSharingIntent.getMediaStream()
.listen((List<SharedMediaFile> value) {
setState(() {
_sharedFiles = value;
print("Shared 1:" + (_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
if (_sharedFiles != null) _goCardAdd((_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
});
}, onError: (err) {
print("getIntentDataStream error: $err");
});
// For sharing images coming from outside the app while the app is closed
ReceiveSharingIntent.getInitialMedia().then((List<SharedMediaFile> value) {
setState(() {
_sharedFiles = value;
print("Shared 2:" + (_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
if (_sharedFiles != null) _goCardAdd((_sharedFiles?.map((f) => f.path)?.join(",") ?? ""));
});
});

"Could not convert: Instance of '_Uri'" error while uploading image on cloud firestore's latest version on flutter web

Getting below error while using the latest version of cloud_firestore: ^0.14.0+2 while uploading image on cloud firestore.
Uncaught (in promise) Error: [cloud_firestore/unknown] Invalid argument (dartObject): Could not convert: Instance of '_Uri'
The image gets successfully uploaded in the storage portion but the image link doesn't get updated in cloud firestore db.
Below is the class from where I pick the image and and hit he upload btn to upload the image
class AddNewItemView extends StatefulWidget {
#override
_AddNewItemViewState createState() => _AddNewItemViewState();
}
class _AddNewItemViewState extends State<AddNewItemView> {
MediaInfo _itemPic;
#override
Widget build(BuildContext context) {
return Scaffold(
.....
child: RawMaterialButton(
onPressed: () async {
MediaInfo pickedImg = await ImagePickerWeb.getImageInfo; <---- image_picker_web package
setState(() {
_itemPic = pickedImg;
});
.........
FlatButton(
onPressed: () async {
bool addDataSuccess = await model.addNewItem( _itemPic);
if (addDataSuccess) {
print("Inside addDataSuccess");
} else {
print("Outside addDataSuccess");
}
},
),
.....
);
}
This class is responsible for extracting image uri from uploadImageFile funtion and uploading it in cloud firestore.
Any other info passed alongside pic gets uploaded in cloud firestore, only 'pic' is not uploading
class ItemViewModel extends BaseViewModel {
Future<bool> addNewItem( MediaInfo pic ) async {
Uri _uploadedImgURL = await ConstantFtns()
.uploadImageFile(pic, "ImgPathString", "fileName");
await FirebaseFirestore.instance.collection('item').doc("tempID").set(
{
'pic': _uploadedImgURL,
'otherInfo': 'otherTempInfo'
},
);
}
in below class I get valid imageUri without any error and image gets uploaded in storage portion only problem is that it don't get uploaded in cloud firestore database
import 'package:firebase/firebase.dart' as fb;
import 'package:mime_type/mime_type.dart';
import 'package:path/path.dart' as p;
class ConstantFtns {
Future<Uri> uploadImageFile(
MediaInfo mediaInfo, String ref, String fileName) async {
try {
String mimeType = mime(p.basename(mediaInfo.fileName));
final String extension = extensionFromMime(mimeType);
var metadata = fb.UploadMetadata(
contentType: mimeType,
);
fb.StorageReference storageReference =
fb.storage().ref(ref).child(fileName + ".$extension");
fb.UploadTaskSnapshot uploadTaskSnapshot =
await storageReference.put(mediaInfo.data, metadata).future;
Uri imageUri = await uploadTaskSnapshot.ref.getDownloadURL();
print("download url $imageUri"); <---- IT GETS P[RINTED AND IT SHOWED VALED IMAGE URL STORED IN STORAGE
return imageUri;
} catch (e) {
print("File Upload Error $e");
return null;
}
}
}
Since what you want is simply to store the URL into firestore you don't really need the getDownloadURL() value to be a Uri object, you just need it's string value, being the error you are getting a conversion error, I suggest that you follow what is recommended in this community answer, that way your code will look like this:
String uploadImageFile(
MediaInfo mediaInfo, String ref, String fileName) async {
try {
...
var imageUri = await uploadTaskSnapshot.ref.getDownloadURL() as String;
print("download url $imageUri"); <---- IT GETS P[RINTED AND IT SHOWED VALED IMAGE URL STORED IN STORAGE
return imageUri
} catch (e) {
...
}
}
})
And you also have to change the call in the ItemViewModel class to:
String _uploadedImgURL = await ConstantFtns()
.uploadImageFile(pic, "ImgPathString", "fileName");

Xamarin.Forms Calling Dialog from Xamarin.Android Project Throws an Exception

I've been trying to call a Dialog Alert from my xamarin.forms app using IoC container so far I haven't been able to display an alert in my app using this code:
Code at my viewModel:
await _dialogService.DisplayMessageAsync("ERROR", "There are errors on your form!", "Cancel", null);
at my shared project I have this DialogService which is the one I call from my viewModel:
public class DialogService : IDialogService
{
readonly IDialogService _dialogService;
public DialogService()
{
_dialogService = DependencyService.Get<IDialogService>();
}
public void CloseAllDialogs()
{
_dialogService.CloseAllDialogs();
}
public async Task DisplayMessageAsync(string title, string message, string buttonCancelName, Action callback)
{
await _dialogService.DisplayMessageAsync(title, message, buttonCancelName, callback);
}
public async Task DisplayMessageConfirmAsync(string title, string message, string buttonOkName, string buttonCancelName, Action<bool> callback)
{
await _dialogService.DisplayMessageConfirmAsync(title, message, buttonOkName, buttonCancelName, callback);
}
}
so at my Xamarin.Android.XXXXX I have the implementation of my DialogService which is call from my DialogService at my Shared Project this is the code:
public class DialogService : IDialogService
{
List<AlertDialog> _openDialogs = new List<AlertDialog>();
public void CloseAllDialogs()
{
foreach (var dialog in _openDialogs)
{
dialog.Dismiss();
}
_openDialogs.Clear();
}
public async Task DisplayMessageAsync(string title, string message, string okButton, Action callback)
{
await Task.Run(() => Alert(title, message, okButton, callback));
}
public async Task DisplayMessageConfirmAsync(string title, string message, string okButton, string cancelButton, Action<bool> callback)
{
await Task.Run(() => AlertConfirm(title, message, okButton, cancelButton, callback));
}
bool Alert(string title, string content, string okButton, Action callback)
{
var activity = (Activity)Forms.Context;
//var activity = (Activity)Android.App.Application.Context;
var alert = new AlertDialog.Builder(Android.App.Application.Context);
//var alert = new AlertDialog.Builder(activity);
alert.SetTitle(title);
alert.SetMessage(content);
alert.SetNegativeButton(okButton, (sender, e) =>
{
if (!Equals(callback, null))
{
callback();
}
_openDialogs.Remove((AlertDialog)sender);
});
Device.BeginInvokeOnMainThread(() =>
{
AlertDialog dialog = alert.Show();
_openDialogs.Add(dialog);
dialog.SetCanceledOnTouchOutside(false);
dialog.SetCancelable(false);
});
return true;
}
bool AlertConfirm(string title, string content, string okButton, string cancelButton, Action<bool> callback)
{
var alert = new AlertDialog.Builder(Android.App.Application.Context);
alert.SetTitle(title);
alert.SetMessage(content);
alert.SetNegativeButton(cancelButton, (sender, e) =>
{
callback(false);
_openDialogs.Remove((AlertDialog)sender);
});
alert.SetPositiveButton(okButton, (sender, e) =>
{
callback(true);
_openDialogs.Remove((AlertDialog)sender);
});
Device.BeginInvokeOnMainThread(() =>
{
var dialog = alert.Show();
_openDialogs.Add(dialog);
dialog.SetCanceledOnTouchOutside(false);
dialog.SetCancelable(false);
});
return true;
}
}
so whenever the private method alert is called it throws an Exception like this:
Unhandled Exception:
Android.Views.WindowManagerBadTokenException: Unable to add window --
token null is not valid; is your activity running?
it can be corrected if I switch this line of code:
var alert = new AlertDialog.Builder(Android.App.Application.Context);
for this line of code:
var activity = (Activity)Forms.Context;
var alert = new AlertDialog.Builder(activity);
the problem of using this I get a Xamarin.Forms warning like this:
'Forms.Context' is obsolete: 'Context is obsolete as of version 2.5.
Please use a local context instead.'
and i'm a bit obsesive I dont like to have warning and try to maintain my code as updated as possible so, can somebody help me make this code work without needing to use obsolete code. because I found answers that just replacing (Activity)Forms.Context for Android.App.Application.Context would work, but in this case it isn't working at all.
hopefully someone can point me in the right direction because i haven't been able to find any documentation about this case specifically.
Problem is that Android.App.Application.Context is not always an Activity Context.
Xamarin removed this and added a new constructor for the Renderers which include the context. The problem comes in cases like this where you are working on something that is not a CustomRenderer.
For these cases, I use James' Plugin CurrentActivityPlugin which will keep track of the current activity for you. Find it here
Hope this helps.-

Plugin.Geolocator exits method (deadlock?)

I'm building a Xamarin app and for the geolocation, I'm using the GeolocatorPlugin
The problem is that once the code wants to get the position, the code exists without warning.
My class fields:
private Position position;
private IGeolocator locator = CrossGeolocator.Current;
My page constructor:
public MainPage()
{
InitializeComponent();
locator.PositionChanged += Locator_PositionChanged;
locator.PositionError += Locator_PositionError;
}
OnAppearing event is calling the getLocationPermission:
private async Task GetLocationPermission()
{
var status = await CrossPermissions.Current.CheckPermissionStatusAsync(Permission.LocationWhenInUse);
if (status != PermissionStatus.Granted)
{
//Not granted, request permission
if (await CrossPermissions.Current.ShouldShowRequestPermissionRationaleAsync(Permission.LocationWhenInUse))
{
// This is not the actual permission request
await DisplayAlert("Need your permission", "We need to access your location", "Ok");
}
// This is the actual permission request
var results = await CrossPermissions.Current.RequestPermissionsAsync(Permission.LocationWhenInUse);
if (results.ContainsKey(Permission.LocationWhenInUse))
status = results[Permission.LocationWhenInUse];
}
//Already granted, go on
if (status == PermissionStatus.Granted)
{
//Granted, get the location
GetLocation();
await GetVenues();
await locator.StartListeningAsync(TimeSpan.FromMinutes(30), 500);
}
else
{
await DisplayAlert("Access to location denied", "We don't have access to your location.", "OK");
}
}
The permission is granted and gets to the GetLocation() method:
private async void GetLocation()
{
//var locator = CrossGeolocator.Current;
try
{
var myPosition = await locator.GetPositionAsync();
position = new Position(myPosition.Latitude, myPosition.Longitude);
}
catch (Exception ex)
{
throw;
}
if (position == null)
{
//Handle exception
}
}
Once the line is reached with locator.GetPositionAsync(), it stops. No exception is thrown, also the PositionError isn't raised.
I have no idea why, but in the beginning it worked once, never worked after that.
The location settings in de Android Emulator are as follow:
Based on my research, you did not acheved that Location Changes like this link
I wrote a demo about Location changes. This is running screenshot.
This is my demo
https://github.com/851265601/GeolocationDemo

OData Connection in Xamarin Form

My code crashes and gives the following error on simulator. It attempts to run the try block in the GetDataFromOdataService() method and throws an error and also issue an alert. I am using Xamarin.Form
using Simple.OData.Client;
using System.Threading.Tasks;
private ODataClient mODataClient;
protected async override void OnAppearing ()
{
base.OnAppearing ();
await InitializeDataService ();
await GetDataFromOdataService();
}
public async Task <bool> InitializeDataService(){
try {
mODataClient = new ODataClient ("http://services.odata.org/Northwind/Northwind.svc/");
}
catch {
await DisplayAlert("Error", "Connection Error", "OK", "Cancel");
System.Diagnostics.Debug.WriteLine("ERROR!");
}
return true;
}
public async Task<bool> GetDataFromOdataService (){
try {
myCustomers= await mODataClient.For("Customers").Top(10).FindEntriesAsync();
}
catch {
await DisplayAlert("Error", "Connection Error", "OK", "Cancel");
System.Diagnostics.Debug.WriteLine("ERROR!");
}
return true;
}
Couple issues:-
In the constructor it was doing var list = new ListView() which constrained it locally than setting the class level scope variable. This was therefore adjusted to list = new ListView().
The other thing, was in the GetTheData function where the items source was being assigned as list.ItemsSource = myList; where it needed changing to list.ItemsSource = Customers;.
I've repackaged the zip file up and sent to you. Let me know if this works for you? You should now be able to see all your customers in the ListView.

Resources