I'm new to flutter, I just want to ensure if the below code is correct, I want to check if the location permission was granted or no, if yes then get the current location and save into shared preferences and THEN go to the homepage route, otherwise go to the location page to ask the user for access his location
#override
void initState() {
super.initState();
checkLocation(context);
}
void checkLocation(context) async {
bool isGranted = await asyncFunction();
if(isGranted)
{
updateSettingLocation();
Navigator.of(context).pushNamed('/homepage');
} else{
Navigator.of(context).pushNamed('/location');
}
}
void updateSettingLocation() async{
final location = await currentLocation();
settingsRepo.setCurrentLocation(location);
}
Future<Position> currentLocation() {
return Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.high)
.then((location) {
if (location != null) {
print("Location: ${location.latitude},${location.longitude}");
}
return location;
});
}
void updateCurrentLocation() async {
Position position = await Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high);
settingsRepo.setCurrentLocation(position);
}
Future<bool> asyncFunction() async {
bool serviceEnabled;
LocationPermission permission;
permission = await Geolocator.checkPermission();
serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (permission == LocationPermission.denied || !serviceEnabled || permission == LocationPermission.deniedForever) {
print('location access is denied');
return false;
} else {
print('location access is granted');
return true;
}
}
As mentioned in this Stack Overflow answer , the following changes should sufficient
#override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) => checkLocation(context));
}
Though I would like to point out that context is not available in initState (unless it's a variable that you've created and are managing)
All the functions defined are correct and the methodology is also fine. It should work with no issues. However I would suggest instead of defining all the functions here in the widget class you should separate it out from the UI by creating a separate class (Example: LocationService) and then initialize that class here and then make use of the functions.
Im working on my first Ionic + Firebase project, and im not understanding this:
Im searching and getting an object from firebase, I can access its details on html and show it to the user.
But now I need to save the createdBy field on that object so I can use it to search for its creator on firebase.
But when I try to access that info its always undefined. Why is that? Any tips on how to fix this?
export class VisitDetailsPage implements OnInit {
public trips: Observable<HomeTripCardsModel>;
public trip: HomeTripCardsModel;
public buddyInfo;
public targetBuddyId: any;
constructor(private router: Router, private navCtrl: NavController,
public fireStorageService: FireStorageService,
private route: ActivatedRoute, public db: AngularFirestore) {
}
ngOnInit() {
const tripId: string = this.route.snapshot.paramMap.get('id');
this.db.collection('users').get()
.subscribe(querySnapshot => {
querySnapshot.forEach(doc => {
this.trips = this.fireStorageService.getTripDetail(tripId, doc.id);
this.trips.forEach((element: HomeTripCardsModel) => {
if (element?.id === tripId) {
this.trip = element;
this.targetBuddyId = element.createdBy;
}
});
});
});
// buddy
console.log(this.trip?.createdBy); // returns undefined
console.log('saved ', this.targetBuddyId) // returns undefined
}}
Data is loaded from Firebase asynchronously. If you set some breakpoints and run in the debugger, or add a log inside the subscribe method, you'll see that your console.log(this.trip?.createdBy) runs before this.trip = element has ever been run. So at that point, it indeed doesn't have a value yet.
For this reason, all code that needs data from the database, needs ot be inside the subscribe callback:
this.db.collection('users').get()
.subscribe(querySnapshot => {
querySnapshot.forEach(doc => {
this.trips = this.fireStorageService.getTripDetail(tripId, doc.id);
this.trips.forEach((element: HomeTripCardsModel) => {
if (element?.id === tripId) {
this.trip = element;
this.targetBuddyId = element.createdBy;
}
});
// buddy
console.log(this.trip?.createdBy); // returns undefined
console.log('saved ', this.targetBuddyId) // returns undefined
});
});
I done this according to Xamarin documentation, just copy-pasted a code.
So, in App.xaml.cs I have a code like this:
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
protected override void OnStart()
{
if (!CheckPermisions())
{
AbortApp(3, "Missing required permissions!");
return ;
}
}
//[...]
public bool CheckPermisions()
{
Task<bool> v = performCheckPermisions();
if (v.Result)
initAppFolders();
return v.Result;
}
protected async Task<bool> performCheckPermisions()
{
// storage read
PermissionStatus status = await Xamarin.Essentials.Permissions.CheckStatusAsync<Permissions.StorageRead>();
if (status == PermissionStatus.Denied)
{
this.Context.ToLogger(EAppLogLevel.Warning, string.Format(" ! StorageRead: requesting..."));
status = await Permissions.RequestAsync<Permissions.StorageRead>();
}
if (status == PermissionStatus.Denied)
return false;
// storage write
status = await Xamarin.Essentials.Permissions.CheckStatusAsync<Permissions.StorageWrite>();
if (status == PermissionStatus.Denied)
{
this.Context.ToLogger(EAppLogLevel.Warning, string.Format(" ! StorageWrite: requesting..."));
status = await Permissions.RequestAsync<Permissions.StorageWrite>();
}
if (status == PermissionStatus.Denied)
return false;
return true; // Task.FromResult(true);
}
The problem is - when application started 1st time, so when the OS asks user for permissions it always hangs! :-(
And I do not understand - why?!
How to resolve this problem with hanging on 1st app run?
I tried to debug it but it never returns from await Permissions.RequestAsync<...>() back into debugger! :-
Of course - on OS request I clicked [Allow] in a UI prompt.
Unfortunately, I'm not sure - why it is not returning, it might be bug in VS2019 debugger or it might be bug in Xamarin... or maybe I'm doing something wrong.
Could you please advice?
Please note: I need exactly the blocking/synchronous call to permissions request! Application must stop and confirm if permissions granted. Without permissions - it must not even try to run.
Note: VS 2019 (16.6.5); Xamarin.Forms 4.7.0.1142; Xamarin.Essentials 1.5.3.2 - so, it seems all the latest.
Thanks.
PS.
Also I tried following variants:
Attempt# 1
Task<bool> tsk = performCheckPermisions();
bool result = false;
if (tsk.IsCompleted)
{
this.Context.ToLogger(EAppLogLevel.Info, string.Format(" . CheckPermisions: task completed without waiting..."));
result = tsk.Result;
}
else
{
TaskAwaiter<bool> aw = tsk.GetAwaiter();
int counter = 0;
while (!aw.IsCompleted)
{
Thread.Sleep(330);
counter++;
if ((counter % 10) == 0)
this.Context.ToLogger(EAppLogLevel.Info, string.Format(" . CheckPermisions: still waiting (#{0})...", counter));
if (counter > 100)
{
AbortApp(99, "Permissions were not comfirmed!");
return false;
}
}
result = aw.GetResult();
}
It simply hang because nor tsk.IsCompleted, nor aw.IsCompleted never became true despite user clicks to [Allow] button.
Attempt# 2
var task = Task.Run(async () => await performCheckPermisions());
if (task.IsFaulted && task.Exception != null)
{
throw task.Exception;
}
bool result = task.Result;
this.Context.ToLogger(EAppLogLevel.Info, string.Format(" ? CheckPermisions: {0}", result));
It reported System.AggregateException exception: Message=One or more errors occurred. (Permission request must be invoked on main thread.); Source=mscorlib.
Attempt# 3
bool result = false;
this.isCompleted = false;
MainThread.BeginInvokeOnMainThread(
async () => {
result = await performCheckPermisions();
this.isCompleted = true;
}
);
int counter = 0;
while (!this.isCompleted)
{
Thread.Sleep(330);
counter++;
if ((counter % 10) == 0)
this.Context.ToLogger(EAppLogLevel.Info, string.Format(" . CheckPermisions: still waiting (#{0})...", counter));
if (counter > 100)
{
AbortApp(99, "Permissions were not comfirmed within specified timeout!");
return false;
}
}
It simply hang. It seems there is bug in Xamarin - the await Permissions.RequestAsync<>() call never return back to application!
Below is the edited code. If you saw an earlier version then you saw it had problems. First entry so am new at this. *
I tried many things to get the permissions but it always hung the App, even with the awaits.
I wanted to put the permission requests as close to where the user required them (As recommended) and not abort the App. This is what I finally came up with:
Creating a permission interface in the Xamarin Forms project
Creating an Android implementation of the permissions in the Xamarin Forms Android project
Registered permission as dependency service in the Android Activity before loading the Forms App
In my WIFI Content page I created an async method that checks permission by calling the registered Dependency service
When I click on WIFI page scan button, it calls the async method to see if the user needs to give permission before continuing
Works like a charm.
The only caveat is that if the user selects 'Don't ask again' he will have to set location services manually. Not sure how I can tell the user since the permissions always only return Denied status. He will get a dialog informing him of insufficient permissions but no OS dialog allowing him to request permissions (Duh, because he said that that he did not want to see them)
The permissions Interface in the Xamarin Forms project
public interface ILocationWhileInUsePermission {
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
Implementation on Xamarin Forms Android side
public class LocationWhileInUsePermission : Xamarin.Essentials.Permissions.BasePlatformPermission, ILocationWhileInUsePermission {
public override (string androidPermission, bool isRuntime)[]
RequiredPermissions => new List<(string androidPermission, bool isRuntime)> {
(Android.Manifest.Permission.AccessFineLocation, true),
(Android.Manifest.Permission.AccessCoarseLocation, true),
(Android.Manifest.Permission.AccessWifiState, true),
(Android.Manifest.Permission.ChangeWifiState, true)
}.ToArray();
}
Register in the Activity.cs OnCreate before load of the App
DependencyService.Register<ILocationWhileInUsePermission, LocationWhileInUsePermission>();
LoadApplication(new App(DI.Wrapper));
In the Wifi Page create functions to invoke permissions from DependencyService and to set results
private bool permissionsGranted = false;
private async Task SetAreGranted(bool granted) {
await Task.Run(() => this.permissionsGranted = granted);
}
public async Task<bool> GetIsGranted() {
return await Task<bool>.Run(() => { return this.permissionsGranted; });
}
public async Task<bool> ChkWifiPermissions() {
try {
await this.SetAreGranted(false);
var wifiPermissions =
DependencyService.Get<ILocationWhileInUsePermission>();
var status = await wifiPermissions.CheckStatusAsync();
if (status != PermissionStatus.Granted) {
status = await wifiPermissions.RequestAsync();
if (status != PermissionStatus.Granted) {
return await this.GetIsGranted();
}
}
await this.SetAreGranted(true);
}
catch (Exception) {
return await this.GetIsGranted();
}
return await this.GetIsGranted();
}
On my WIFI Content Page, on the button click event I call the async method
private void btnDiscover_Clicked(object sender, EventArgs e) {
Device.BeginInvokeOnMainThread(async () => {
if (await this.ChkWifiPermissions()) {
this.btnSelect.IsVisible = false;
this.ResetWifiList(new List<WifiNetworkInfo>());
this.activity.IsRunning = true;
App.Wrapper.WifiDiscoverAsync();
}
else {
this.OnErr("Insufficient permissions to continue");
}
});
}
Just to add a variance to my WIFI permissions check on WIFI scan button, here is a variance that aborts on App start. It works but I prefer the one that requests closer to the usage of the permission.
This works and never hangs the App. Still the problem if the user has requested not to be asked again.
Start by declaring service interfaces in the Xamarin Forms project to close the App, and another to check and request permissions
public interface ICloseApplication {
void CloseApp();
}
public interface ILocationWhileInUsePermission {
Task<PermissionStatus> CheckStatusAsync();
Task<PermissionStatus> RequestAsync();
}
Then add Android OS implmentations in the Xamarin Forms Android project
public class AndroidCloseApp : ICloseApplication {
public void CloseApp() {
Android.OS.Process.KillProcess(Android.OS.Process.MyPid());
}
}
public class LocationWhileInUsePermission : Xamarin.Essentials.Permissions.BasePlatformPermission, ILocationWhileInUsePermission {
public override (string androidPermission, bool isRuntime)[]
RequiredPermissions => new List<(string androidPermission, bool isRuntime)> {
(Android.Manifest.Permission.AccessFineLocation, true),
(Android.Manifest.Permission.AccessCoarseLocation, true),
(Android.Manifest.Permission.AccessWifiState, true),
(Android.Manifest.Permission.ChangeWifiState, true)
}.ToArray();
}
Register the Services in the Xamarin Forms Android project MainActivity.OnCreate(). BTW, the DI.Wrapper has the results from my dependency injector, with common and OS specific code
DependencyService.Register<ILocationWhileInUsePermission, LocationWhileInUsePermission>();
DependencyService.Register<ICloseApplication, AndroidCloseApp>();
LoadApplication(new App(DI.Wrapper));
Then in the Xamarin Forms project, in the App.OnStart() override method call an async method to request permissions and abort if necessary
protected override void OnStart() {
// This will abort the app at the start if the WIFI permissions are not given
Device.BeginInvokeOnMainThread(async () => {
if (!await this.CheckPermissions()) {
ICloseApplication closeApp = DependencyService.Get<ICloseApplication>();
await Application.Current.MainPage.DisplayAlert(
App.GetText(MsgCode.Error),
"Insufficient permissions",
App.GetText(MsgCode.Ok));
closeApp.CloseApp();
}
});
}
private async Task<bool> CheckPermissions() {
ILocationWhileInUsePermission wifiPermissions =
DependencyService.Get<ILocationWhileInUsePermission>();
PermissionStatus status = await wifiPermissions.CheckStatusAsync();
if (status != PermissionStatus.Granted) {
status = await wifiPermissions.RequestAsync();
}
return status == PermissionStatus.Granted;
}
As Cheesebaron mentioned, you always want to use await when dealing with a Task. You can modify your example like so:
public partial class App : Application
{
public App()
{
InitializeComponent();
MainPage = new NavigationPage(new MainPage());
}
protected override async void OnStart()
{
bool result = await CheckPermisions()
if (!result)
{
AbortApp(3, "Missing required permissions!");
return ;
}
}
//[...]
public async Task<bool> CheckPermisions()
{
bool v = await performCheckPermisions();
if (v)
initAppFolders();
return v;
}
protected async Task<bool> performCheckPermisions()
{
// storage read
PermissionStatus status = await Xamarin.Essentials.Permissions.CheckStatusAsync<Permissions.StorageRead>();
if (status == PermissionStatus.Denied)
{
this.Context.ToLogger(EAppLogLevel.Warning, string.Format(" ! StorageRead: requesting..."));
status = await Permissions.RequestAsync<Permissions.StorageRead>();
}
if (status == PermissionStatus.Denied)
return false;
// storage write
status = await Xamarin.Essentials.Permissions.CheckStatusAsync<Permissions.StorageWrite>();
if (status == PermissionStatus.Denied)
{
this.Context.ToLogger(EAppLogLevel.Warning, string.Format(" ! StorageWrite: requesting..."));
status = await Permissions.RequestAsync<Permissions.StorageWrite>();
}
if (status == PermissionStatus.Denied)
return false;
return true; // Task.FromResult(true);
}
}
This is a class in a seperate file
import 'package:sms/sms.dart';
class SmsReader {
SmsMessage sms;
dynamic messageRead() async {
SmsReceiver receiver = SmsReceiver();
receiver.onSmsReceived.listen((SmsMessage msg) {
sms = msg;
});
return sms;
}
}
this file is where an object is created and func is called
class _HomePageState extends State<HomePage> {
SmsReader smsReader = SmsReader();
dynamic sms;
#override
void initState() {
sms = smsReader.messageRead();
super.initState();
}
Now when I try to
print(sms.body), this error appears
Class 'Future' has no instance getter 'body'.
Receiver: Instance of 'Future'
Tried calling: body
You are defining messageRead as:
dynamic messageRead() async {
All async functions should return a Future, you are returning dynamic which defines that "every Type is ok here".
later you are calling messageRead without await, so it returns just a Future. You need to change your code to await messageRead:
Future<SmsMessage> messageRead() async {
...
SmsMessage sms;
...
smsReader.messageRead().then((newSms) => sms = newSms);
You need to use then instead of await as you can't await inside the initState function.
Do the change
Future<dynamic> messageRead() async {
SmsReceiver receiver = SmsReceiver();
receiver.onSmsReceived.listen((SmsMessage msg) {
sms = msg;
});
return sms;
}
and
smsReader.messageRead().then((sms) {
//do whatever with sms ******
});
Let me know if it works.
I want to ask why my streamsubscription does not print anything
I use StatefulWidget and declare StreamSubscription
StreamSubscription _triggerOrder;
and then I put my function on init state
#override
void initState() {
super.initState();
_triggerOrder=firestore.collection('users').document(widget.idUser).snapshots().listen((trigger){
print("PENDING==========> "+trigger.data['pending']);
if(trigger.data['pending'] == true && _oderTrigger == false){
_modalBottomSheetMenu(context);
setState(() {
_oderTrigger = true;
});
}
});
}
I want to show modal if the data pending from firebase is true. And when I try to print, it does not print the pending.