Geolocation GetLastKnownLocationAsync() permission exception from Xamarin Forms - call fails and no permission prompt - xamarin.forms

GetLocationAsync fails on my Xamarin.Forms app.
I've got the latest Xamarin.Essentials nuget package.
I've set the necessary permissions in the info.plist.
I am calling this from my ViewModel.
The call is super simple:
var location = await Geolocation.GetLastKnownLocationAsync();
but it's both failing AND failing to prompt a user permission dialog even though my info.plist has been setup correctly with:
NSLocationWhenInUseUsageDescription
Insert reason
I'm asking and answering this question because it was a head scratcher, and I wasn't exactly sure what to be searching for or what the issue was.
My various searches pointed to many related issues but nothing that actually gets to the main problem.
The closest I got was actually this issue on the Essentials github page:
https://github.com/xamarin/Essentials/issues/634

This answer is inspired by Xamarin/Azure evangelist, Brandon Minnick --> take a look at his project where he handles a similar situation with the following code:
So what can we take away from the above? If you look at the context, he has connected his Views with his ViewModels in MVVM style. However, various libraries require that certain methods be called from the Main thread. This is the essence of the issue, and this is what this code can solve.
So to adopt the above code for the geolocation issue addressed in the question, I did the following:
Task<Xamarin.Essentials.Location> GetLocationFromPhone()
{
var locationTaskCompletionSource = new TaskCompletionSource<Xamarin.Essentials.Location>();
Device.BeginInvokeOnMainThread(async () =>
{
locationTaskCompletionSource.SetResult(await Geolocation.GetLastKnownLocationAsync());
});
return locationTaskCompletionSource.Task;
}
I'm using the above from my ViewModel from within a Task. Something like the following.
async Task ExecuteGetGeoLocationCommand()
{
try
{
var locationFromPhone = await GetLocationFromPhone().ConfigureAwait(false);
if (locationFromPhone is null)
return;
_location = locationFromPhone;
if (_location != null)
{
Console.WriteLine($"Latitude: {_location.Latitude}, Longitude {_location.Longitude}, Altitude: {_location.Altitude}");
}
else
{
Console.WriteLine($"Exiting geolocation");
}
catch (FeatureNotSupportedException fnsEx)
{
}
catch (Exception ex)
{
}
}
}
I hope it's helpful to someone else!

If you're using Xamarin.Essentials and aren't being prompted for permission on Android, make sure you've added all the necessary code to the Android Main Activity.
See https://learn.microsoft.com/en-us/xamarin/essentials/get-started?tabs=windows%2Candroid for details.
From the docs:
protected override void OnCreate(Bundle savedInstanceState) {
//...
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState); // add this line to your code, it may also be called: bundle
//...
and
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}

Related

Background Service in Xamarin.Forms, iOS, Android

I have a background service in android.
My code is as follows:
[Service]
public class PeriodicService : Service
{
public override IBinder OnBind(Intent intent)
{
return null;
}
public override StartCommandResult OnStartCommand(Intent intent, StartCommandFlags flags, int startId)
{
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
// code
});
return StartCommandResult.Sticky;
}
}
The MainActivity class:
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
global::Xamarin.Forms.Forms.Init(this, bundle);
LoadApplication(new App());
StartService(new Intent(this, typeof(PeriodicService)));
}
Permission AndroidManifest.xml:
<uses-permission android:name="android.permission.PeriodicService" />
The problem is that my service only works in the background when the app is active or in the foreground but when I close the app it doesn't run in the background.
A possible solution is to follow along with what Fabio has written in his post about how to create The Never Ending Background Task.
The idea behind this is to create a BroadcastReceiver and Android Service in the manifest. The BroadcastReceiver will then activate the service with foreground priority as the application is being closed. If you are dealing with post Android 7 then he created an update to his blog showing The Fix.
Just to get a better understanding of how basic Android services operate, I'd recommend taking a look at this Android Services Tutorial
I also saw this other StackOverflow post that seems to be pretty similar so maybe there will be some useful info over there too.
I'm not too acclimated in this stuff, but I was able to follow along with the blog posts pretty easily so I hope this ends up helping :). If anyone finds a better solution I'd love to hear about it so tag be (if you would be so kind) so I can stay updated!

How to retrieve limited documents in firestore depending on the time they were added(For a chat app)?

Basically I want to align the messages as they are supposed to be in the usual chat app.
UPDATE: Now messages are aligned properly in the recyclerview. But whenever I send the new message it puts them on the top of the other messages. And whenever I go back and come again to that activity messages are arranged properly(even the top ones).
I just want the message which I send to show up at the bottom. Any help will be appreciated.
mLinearLayout.setReverseLayout(true);
Then:
private void loadmessage() {
mFirestore.collection("Users").orderBy("Timestamp", Query.Direction.DESCENDING).limit(10).addSnapshotListener(new EventListener<QuerySnapshot>() {
#Override
public void onEvent(#Nullable QuerySnapshot snapshots,
#Nullable FirebaseFirestoreException e) {
if (e != null) {
Log.w("TAG", "listen:error", e);
return;
}
for (DocumentChange dc : snapshots.getDocumentChanges()) {
switch (dc.getType()) {
case ADDED:
Message message = dc.getDocument().toObject(Message.class);
messageList.add(message);
mAdapter.notifyDataSetChanged();
mMessagesList.scrollToPosition(messageList.size()-10);
break;
}
}
}
});
}
You should continue retrieving the information as you are currently doing, and then invert the order on client side, it isn't an expensive task, and trying to do what you want in Firestore is not implemented yet.
After retrieving the list, you can do something like:
Collections.reverse(messageList.clone());
Here is the same case scenario and it has the answer too. So in case anyone looking for the answer.
And This is the github repository for the features of the firestore chat app.

How to open the app privacy settings from xamarin forms

Trying to open my app settings , so the user can see the permissions my app is required.
cant find anything with a similar example.
*
if (item.Name == "Privacy preferences")
{
switch (Device.RuntimePlatform)
{
case Device.iOS:
Device.OpenUri(
new Uri(FORGOT WHAT TO PUT IN HERE .. APP/SETTINGS?);
break;
case Device.Android:
Device.OpenUri(
new Uri();
break;
}
*
Fairly simple, you will have to create platform specific implementations.
The Interface
public interface ISettingsHelper
{
void OpenAppSettings();
}
iOS
public void OpenAppSettings()
{
var url = new NSUrl($"app-settings:{app_bundle_id}");
UIApplication.SharedApplication.OpenUrl(url);
}
Android
public void OpenAppSettings()
{
var intent = new Intent(Android.Provider.Settings.ActionApplicationDetailsSettings);
intent.AddFlags(ActivityFlags.NewTask);
var uri = Android.Net.Uri.FromParts("package", package_name, null);
intent.SetData(uri);
Application.Context.StartActivity(intent);
}
From Xamarin.Forms project you could simple call OpenAppSettings();.
P.S.: Please keep in mind that this solution requires tweaking if you would like it to work on older devices.
Try Device.OpenUri(new Uri("app-settings:"));
If this doesn't work (I think it has a while ago), you probably have to do this in your platform specific parts and use e.g. the dependency service. For IOS then use UIApplication.SharedApplication.OpenUrl(new NSUrl("app-settings:"));
Edit: #EvZ's answer is the one with platform specific code and the abstraction, if you also need UWP you can call
await Windows.System.Launcher.LaunchUriAsync(new Uri("ms-settings:privacy-location"));

Best place to load data when navigating XamarinForms Prism 6?

One of the cool features in Prism 6 is the deep linking and passing parameters. In a lot of cases, you'd want to use this parameter to look up data from a web service. Ideally this would be using async/await to get the data. Where is the best place to do this? The OnNavigatedTo method for example is a void.
Although I don't have a case for Deep Linking yet, I am doing many loads on many pages inside OnNavigatedTo and it is working great!
Here is a sample:
public void OnNavigatedTo(NavigationParameters parameters)
{
if (parameters != null &&
parameters.ContainsKey("MyKey"))
{
SomePrivateFieldInViewModel = (YourVariable)parameters["MyKey"];
//SomeWork
}
GetItems();
}
private async void GetItems()
{
try
{
SomeListInViewModel = await WebServices.GetEntity(SomePrivateFieldInViewModel);
//SomeWork
}
catch (Exception ex)
{
//SomeWork
}
}

In Firebase SDK for Java createUser() does not return uid

I am trying to figure out a clean and simple way to obtain the uid resulting from a call to createUser() when working with the Java SDK. This is easy to do when working with the Javascript SDK (e.g., this question/answer). I've also seen the question raised on Stackoverflow in the context of the Firebase iOS API where it apparently isn't so easy. My problem is how to do it using the Firebase Java SDK. Specifically, my use case is the same as that in the iOS-related question, i.e. allow an Admin user to create user accounts (email/password authentication) and also store other info about the created user in Firebase with the uid as the key. Knowing and using the uid as a key is essential in that it is the basis for the security rules.
I've seen a couple of proposed solutions, both of which involved some procedure to be carried out after the new user account has been created. These are
query the Firebase using the email address
Login as the new user and use the authData to get the uid
Either way I have a convoluted solution with multiple async callbacks to deal with an issue that is trivial when using the Javascript API.
I therefore have three specific questions:
Is there currently a better approach than the two I've listed above?
If I use the 2nd approach and login as the newly created user, doesn't that over-ride the Admin token (i.e., log-out the admin who created the user) which in turn means new security rules apply?
Is there any expectation that the Android & Java SDK's will be upgraded anytime soon so that the createUser() API is the same as the Javascript version?
UPDATE: Digging deeper and experimenting a bit I found the answers to my questions. It turns out that the API documentation provided by Firebase is out of date and/or inconsistent.
Re Q1: According to the Javadoc for createUser() the the only available callback handler is a Firebase.ResultHandler. However according to the Changelog, the API Reference document, and the documentation on Creating User Accounts, a Firebase.ValueResultHandler may be used as well. This provides direct access to the UID
Re Q2: The answer is yes. Authenticating the newly created user account results in the replacement of the auth token.
Re Q3: The real question should be "When are the Firebase folks going to update the Javadoc?" Or maybe a better question is "Why are new versions of the SDK being released without updated and accurate documentation?"
The following code is the correct way to deal with creating a new user
Firebase ref = new Firebase("https://<YOUR-FIREBASE>.firebaseio.com");
ref.createUser("harry#foo.com", "badPassword", new Firebase.ValueResultHandler<Map<String, Object>>() {
public void onSuccess(Map<String, Object> result) {
System.out.println("New account with uid: " + result.get("uid"));
}
public void onError(FirebaseError firebaseError) {
// there was an error
}
});
I've updated the question to explain the reasons.
Try this. This is for the newer version of Firebase that came out in the most recent Google I/O. I am not promoting this new version or putting the older version down. I am just stating this as an alternative to the solution above:
mAuth = FirebaseAuth.getInstance();
//creates the user with email and password...make this another type of login if you want
mAuth.createUserWithEmailAndPassword(mEmail, mPassword).addOnCompleteListener(signup.this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
//do something
}
}
});
Now you can add an AuthStateListener. You will have to put code in the onCreate, onStart, and onStop methods. Note that the above code can go in any reasonable method (e.g. onCreate, onStart, onResume, etc.). Here we go:
FirebaseAuth mAuth;
FirebaseAuth.AuthStateListener mAuthListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_something);
mAuth = FirebaseAuth.getInstance();
mAuthListener = new FirebaseAuth.AuthStateListener() {
#Override
public void onAuthStateChanged(#NonNull FirebaseAuth firebaseAuth) {
FirebaseUser user = firebaseAuth.getCurrentUser();
if(user != null){
//check for null to prevent NullPointerException when dealing with user
if(!user.getUid().matches("")){
//make this check just in case...I've experienced unexplainable glitches here
String uid = user.getUid();
//do more stuff with Uid
}
}
}
}
}
#Override
public void onStart(){
super.onStart();
mAuth.addAuthStateListener(mAuthListener);
}
#Override
public void onStop(){
super.onStop();
if(mListener != null){
mAuth.removeAuthStateListener(mAuthListener);
}
}
In the end, what happens is, once the user is created (and signed in at the same time), the mAuthListener makes a callback (it executes whatever is inside the mAuthListener, which, in this case, is getting the user's UID and doing other stuff with it). If you need help with this new Firebase in Android, here is a link to help you out: https://firebase.google.com/docs/android/setup

Resources