Xamarin Forms - limiting TapGestureRecognizer to a single tap - xamarin.forms

I have a TapGestureRecognizer on a Label. All works great with the following Command handler (using FreshMVVM PushPageModel which navigates to the next page)...
public Command OpenDailyEntry
{
get
{
return new Command(async (e) =>
{
await CoreMethods.PushPageModel<DailyEntryPageModel>(null);
});
}
}
The problem is the next screen takes a second or so to appear, and before that happens the user can tap multiple times causing the event to fire multiple times. On Android this results in the same page being opened each time the user taps the label.
Is there a way to prevent this without some global flag that disables the Command after the first tap?

You could add a second lambda for the CanExecute method of your Command.
If you add a property to your page navigation service, e.g. IsCurrentlyPushingPage, which keeps track of when the pushing to the navigation stack starts and when it ends, you can evaluate this property and deactivate the OpenDailyEntry command temporarily.
public Command OpenDailyEntry
{
get
{
return new Command(async (e) =>
{
await CoreMethods.PushPageModel<DailyEntryPageModel>(null);
},
() =>
{
return CoreMethods.IsCurrentlyPushingPage == false;
}
);
}
}

Related

How to know the popped state of a NavigationPage in Xamarin

I want it to popup or not to popup by receiving the On and Off events.
I received the On event and popped it up. Off event has [await Navigation.PopAllPopupAsync(false); ] to clear the popup.
There are cases where the off event comes in twice. If there is no currently popped page, await Navigation.PopAllPopupAsync(false); will throw an exception. So I want to know if there is a flag to know if there is currently popped screen.
How to know which page is currently popped up
Fix #1:
You could make the exception harmless:
try
{
await Navigation.PopAllPopupAsync(false);
}
catch (Exception ex)
{
}
Fix #2:
You could keep track of it yourself:
public static bool PopupIsShowing;
// In On event, Just before (or just after) you show it.
PopupIsShowing = true;
// In Off event,
if (PopupIsShowing) {
PopupIsShowing = false;
await Navigation.PopAllPopupAsync(false);
}

Xamarin Forms: How to fetch the latest contacts of phone after adding a number to phonebook?

I have referred to this blog for listing the phone contacts. Also, I have implemented adding contacts to the phonebook using DependencyService as per this thread.
My problem is after adding a contact to the device phone book I need to fetch the entire latest contacts. Also, I need to show the new contact on the contact listview.
For reference, I have created a sample project and uploaded it here. In this sample first of all I am listing the phone contacts with the Add New option on the top. If we tap Add New, a new page will open with the Add to Contact option and entry for phone number. Click the Add to Contact option after entering a phone number, then the device phonebook page will show with the phone number entered.
At this stage, the user may or may not save that number to the device phonebook. So when the user resuming to the App I need to fetch the entire contacts and check the phone number is added or not to the device phone book. If contact added I will hide the Add to Contact option else I will show that option again. At the same time when the user going back to the contact list, I need to show the newly added contact over there.
For this, I have added a message on App.xaml.cs and Subscribe it on AddContactPage.
App.xaml.cs
protected override void OnResume()
{
// Handle when your app resumes
MessagingCenter.Send(this, "isContactAdded");
}
AddContactPage
MessagingCenter.Subscribe<App>(this, "isContactAdded", (sender) =>
{
//How I can fetch the entire latest contacts here
//After fetching conatcts only I can check the new phone number is added to the device phonebook
});
The contacts are passing as an argument from MainActivity to App.xaml.cs. So I don't know how to fetch it directly. Is there any way to fetch all contacts on this page?
//loading all the new contacts
protected async override void OnResume()
{
if (Utility.isContactAdding)
{
UserDialogs.Instance.ShowLoading("");
List<Contact> contacts = await contactsService.RetrieveContactsAsync() as List<Contact>;
MessagingCenter.Send<App, List<Contact>>(App.Current as App, "isContactAdded", contacts);
}
}
//Comparing the new contacts with phone number
MessagingCenter.Subscribe<App, List<Contact>>(App.Current, "isContactAdded", (snd, arg) =>
{
Device.BeginInvokeOnMainThread(() =>
{
Utility.ContactsList.Clear();
phone = Regex.Replace(phone, #"[^0-9+]+", "");
bool isContactExist = false;
var AllNewContacts = arg as List<Contact>;
foreach(var item in AllNewContacts)
{
if (item.PhoneNumbers.Length != 0)
{
foreach(var number in item.PhoneNumbers)
{
if (number.Replace("-", "").Replace(" ","") == phone)
{
isContactExist = true;
}
}
}
Utility.ContactsList.Add(item);
}
if (isContactExist)
{
Phonebook_layout.IsVisible = false;
MessagingCenter.Send<CallHistoryDetailPage>(this, "refreshcontacts");
}
else
{
Phonebook_layout.IsVisible = true;
}
Utility.isContactAdding = false;
UserDialogs.Instance.HideLoading();
});
});
//Subscribed message and refershing the contacts
MessagingCenter.Subscribe<CallHistoryDetailPage>(this, "refreshcontacts", (sender) =>
{
BindingContext = new ContactsViewModel(Utility.myContacts);
});

Await method before app starts in the same UI thread

I'm trying to check which page should load my app at the beginning, first of all I check a database table if I find the login information stored I want to push the once named StartPage(), as I'm working with the database the method includes an await if there isn't any data stored I want to push the LoginPage(). I have tried following this example Xamarin.Forms Async Task On Startup . My code is :
public App()
{
int result;
InitializeComponent();
ThreadHelper.Init(SynchronizationContext.Current);
ThreadHelper.RunOnUIThread(async () => {
MainPage = new ActivityIndicatorPage();
result = await InitializeAppAsync();
if (result == 0)
{
PushLoginPage();
}
else
{
PushStartPage();
}
});
}
public void PushStartPage()
{
NavigationPage nav = new NavigationPage(new StartPage());
nav.SetValue(NavigationPage.BarBackgroundColorProperty, Color.FromHex("#D60000"));
MainPage = nav;
}
public void PushLoginPage()
{
MainPage = new Login();
}
public void PushLoginPage(string email, string password)
{
MainPage = new Login(email, password);
}
private async Task<int> InitializeAppAsync()
{
if (ViewModel == null)
ViewModel = new MainViewModel(this);
return await ViewModel.LoginViewModel.PushInitialPage();
}
But throws the following exception and as the author of the article says, is not recommended to do it.
Exception
Another option tried was overriding the OnStart() method but didn't work either.
protected override async void OnStart()
{
Task.Run(async ()=> { await InitializeAppAsync(); });
}
The PushInitialPage method:
public async Task PushInitialPage()
{
if (_app.Properties.ContainsKey("isLogged"))
{
var user = await UserDataBase.GetUserDataAsync();
var result = await Login(user.Email, user.Password);
if (result.StatusCode != 200)
{
return 0;
///PushLoginPage();
}
else
{
return 1;
//PushStartPage();
}
}
else
{
return 0;
}
}
When the OS asks your app to show a page, it must show a page. It can't say "hold on a minute or two while I talk to this remote server over an iffy network connection." It has to show a page Right Now.
So, I recommend bringing up a splash page - your company or app logo, for example. When the splash page shows, then call InitializeAppAsync, and based on the result, switch to the login or start page or nice user-friendly offline error page.
In Xamarin.Forms we have properties called 'Application.Current.Properties'. By using this we can able to save the any data type. So once user login in to the application you can set one flag and set it is true. Then after every time when user login in to the application you can check this flag and navigate your respective page.
Sample Code :
App.cs :
public App()
{
if (Current.Properties.ContainsKey("isLogged"))
{
if((bool)Application.Current.Properties["isLogged"])
{
// navigate to your required page.
}
else
{
// naviate to login page.
}
}
else
{
// naviate to login page.
}
}
At first time application open it checks the 'isLogged' property is presented or not, if not it will move to the login page. When user login into the application by using his credentials, we need to create 'isLoggin' property and set as true. Then after if user try to login it checks the condition and navigates to the respective page.
Saving Property SampleCode :
Application.Current.Properties["isLogged"] = true;
await Application.Current.SavePropertiesAsync();
write above code for after login into the application. If a user log out from the app you need to set 'isLogged' flag is false.

Unable to remove Firebase node

I'm at a loss on this one. I'm unable to delete a node in Firebase either from the web app or through the Firebase console. When the /courses/{courseId}/members/{uid} node is removed, either through set(null) or remove(), the user's information is added immediately.
I do have two cloud functions updating the seats node so we can keep track of space, but neither of those point at the ../{uid} endpoint. I've also gone through my web app code to make sure there were no on_delete events writing to the tree. Any clues as to what's happening?
UPDATE
After going backward in my commit tree, I was able to confirm that it was the below cloud function disallowing deletes from the tree. So, my question now becomes, what in this function causing the behavior? I can't see anything that would re-write data. My database structure is below.
/courses ref
courses: {
someStringId1234: {
members: {
uid12345: {
code: 'someString',
email: 'some#email.com'
},
uid67890: { ... }
},
seats: 10
},
{ ... }
}
This cloud function watches for changes to the uid item. It grabs the parent node (members) and then updates the count in seats. For some reason, this is re-writing the previously deleted key and setting it to 0.
countRegistrations, firebase cloud function
exports.countRegistrations = functions.database.ref('/courses/{courseId}/members/{uid}').onWrite(
(change) => {
// get the `members` node for the item
const collectionRef = change.after.ref.parent;
console.log('collectionRef', collectionRef)
// get the `seats` key for updating
const countRef = collectionRef.parent.child('seats');
console.log('countRef', countRef)
let increment;
// If the ID is there after but not before, remove one seat
if (change.after.exists() && !change.before.exists()) {
increment = -1;
// if the ID is not there after, but there before, add one seat
} else if (!change.after.exists() && change.before.exists()) {
increment = 1;
} else {
// Nothing to change
return null;
}
// Return the promise from countRef.transaction() so the function
// waits for this async event to complete before it exits.
return countRef.transaction((current) => {
console.log('Line 38', current) // debugging, confirms the correct refs are grabbed by the function
return (current || 0) + increment;
}).then(() => {
return
});
});
Just for fun, here's what happens when I try to delete the node directly in the console. The database rules allow writing if you're logged in.
I had the same issue. Turns out that by deleting that node would cause too many firebase functions to execute. Try use the delete command in the CLI, it might give you more information on the error. firebase database:remove <//path to node>
I had same issue, and I just fixed it. The error occurred because of the node data it is using.
Please check where you use the node current, and break or sign out. Then, it is possible to delete the node.
Explanation: If the node is user info and current the user is logged in, it is impossible to delete the node. Continuously, re-created node as long as deleting node.
For this scenario, i am using handler to delay the refresh of my activity with 2-3 seconds, If anyone is getting this error then make sure:-
Do not make the refresh of an activity or display (reloading the database)
Use handler with 2-3 seconds delay, then the logged in user can't auto remake the node.
How i used to delete the node:-
myViewHolder.deleteicon.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
String childvalue = list.get(i).getPosition();
Log.d(TAG, "onClick: "+ childvalue);
FirebaseDatabase.getInstance().getReference()
.child("Submission/Rules").child(categorynametext).child(user.getUid()).child(childvalue).removeValue()
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()) {
Toast.makeText(context, "You have removed successfully", Toast.LENGTH_LONG).show();
try
{
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
///Here i am refreshing the activity
((Activity)context).finish();
((Activity)context).overridePendingTransition( 0, 0);
((Activity)context).startActivity(((Activity) context).getIntent());
((Activity)context).overridePendingTransition( 0, 0);
}
}, 3000);
}
catch (Exception e){
e.printStackTrace();
}
} else {
//enter msg or enter your code which you want to show in case of value is not remove properly or removed failed.
Toast.makeText(context, "There is an error while removing the rule, Please try again later", Toast.LENGTH_LONG).show();
}
}
});
}
});
UPDATE:
After working more in this, i found i was also getting the same error, because of using addValueEventListener< in this case it is auto adding the data while deleting or adding something in the database and if you use addListenerForSingleValueEvent, then there will not any problem while adding or deleting the data.
Final Conclusion:
addValueEventListener (If you want to refresh the page everytime any add or deletion occurs
addListenerForSingleValueEvent(If you don't want to refresh the page while adding or deleting something)

Windows Phone 8.1 universal app, Navigate to a particular on tapping of receive Push Notification

I am sending Push notification from Azure Notification hub. I want to navigate to particular page on tap of received Toast Push Notification. I am receiving Push notification but unable to navigate to a particular page.
Here is my insert code:
function insert(item, user, request) {
var payload = '<?xml version="1.0" encoding="utf-8"?><toast><visual>' +
'<binding template="ToastText01"> <text id="1">' +
item.subject + '</text></binding></visual></toast>';
request.execute({
success: function () {
// If the insert succeeds, send a notification.
push.wns.send(null, payload, 'wns/toast', {
success: function (pushResponse) {
console.log("Sent push:", pushResponse);
request.respond();
},
error: function (pushResponse) {
console.log("Error Sending push:", pushResponse);
request.respond(500, { error: pushResponse });
}
});
}
});
}
Can any one please help?
There is a number of steps here and you didn't give very much detail on your problem. I'll try to explain the concept in full of anyone who might need the whole thing. Be sure you've set up all the steps in this article first: https://azure.microsoft.com/en-us/documentation/articles/mobile-services-javascript-backend-windows-phone-get-started-push/
First, you need to send the push notification with the page you want to load. So let's say you have a page that shows some details about an item. When you get a push notification, it automatically opens up that item. You could send a payload like:
var payload = '<?xml version="1.0" encoding="utf-8"?><toast><visual>' +
'<binding template="ToastText01"> <text id="1">' +
item.id + '</text></binding></visual></toast>';
Then you need to respond to the push notification. You can see the documentation page for this here: https://msdn.microsoft.com/en-us/library/hh221550.aspx
Your set up code would look something like this:
ShellToastNotificationReceived +=
new EventHandler<NotificationEventArgs>(httpChannel_ShellToastNotificationReceived);
public static HttpNotificationChannel CurrentChannel { get; private set; }
// This is from the tutorial linked in the first paragraph
private void AcquirePushChannel()
{
CurrentChannel = HttpNotificationChannel.Find("MyPushChannel");
if (CurrentChannel == null)
{
CurrentChannel = new HttpNotificationChannel("MyPushChannel");
CurrentChannel.Open();
CurrentChannel.BindToShellToast();
}
CurrentChannel.ChannelUriUpdated +=
new EventHandler<NotificationChannelUriEventArgs>(async (o, args) =>
{
// Register for notifications using the new channel
await MobileService.GetPush()
.RegisterNativeAsync(CurrentChannel.ChannelUri.ToString());
});
CurrentChannel.ShellToastNotificationReceived +=
new EventHandler<NotificationEventArgs(async (o, args) =>
{
RootFrame.Navigate(new Uri("/ItemPage.xaml"), args.Collection['1']);
});
}
I haven't tested any of this code, but it should be good enough to point you in the right directions. Basically,
Send the info you need to react in the Push Notification
Listen for event on your Client
Navigate to the frame you want to be on
Be sure to checkout this tutorial on Navigation: https://msdn.microsoft.com/en-us/library/windows/apps/xaml/hh771188.aspx

Resources