Xamarin Forms Display Alert Disappear - c#

i'm developing an app in Xamarin Forms, but i've noticed a strange behavior, when i display an alert, in Android (version major than 4.2.2), if i press outside the alert modal, the alert disappear immediately. There is any way to prevent this? I want that the alert disappear only on user selection.
Many thanks

When a dialog is shown in Android, clicking just outside of it to cancel is pretty standard. To change this behavior you will need to create an interface in your PCL:
public interface ICustomAlert
{
void ShowAlert(string message);
}
In your Android project create the implementation (something like this):
[assembly: Xamarin.Forms.Dependency (typeof (AndroidCustomAlert))]
public class AndroidCustomAlert : ICustomAlert
{
void ShowAlert(string message)
{
var builder = new AlertDialog.Builder(Xamarin.Forms.Forms.Context);
builder.SetMessage(message);
builder.SetPositiveButton("OK", (sender, args) => { });
builder.SetCancelable(false);
builder.Show();
}
}
Notice the SetCancelable(false). That's what makes it so the user can't click outside of the alert and make it disappear.
To use it, get ICustomAlert from the dependency service and call ShowAlert:
DependencyService.Get<ICustomAlert>().ShowAlert("Hello!");

Implement your own DisplayAlert with a PopupLayout.
OR
Make use of ACR User Dialogs Plugin for Xamarin by Allan Ritchie

Related

How to swipe back in Xamarin.iOS?

I have a ContentPage with a WebView in my shared Xamarin project.
To enable Android's back button within the WebView, I used the following code:
protected override bool OnBackButtonPressed()
{
_myWebView.GoBack();
return true;
}
As we all know, iOS does not have a back button. I want to enable swiping left in order to navigate to the previous Webview page.
What is the easiest way or approach?
I would suggest implementing a custom renderer for the WebView and attach a gesture recognizer to the underlying UIWebView. This is how the WebViewRenderer for iOS is implemented in Xamarin Forms.
The Xamarin Forms WebView is actually a UIWebView on iOS. So you can implement the Swift code from the following answer in your renderer: https://stackoverflow.com/a/32012660/6192895. Converting this Swift code to C# is fairly trivial, but this is also a potential solution:
Basic solution
public class SwipeWebViewRenderer : WebViewRenderer
{
protected override void OnElementChanged(VisualElementChangedEventArgs e)
{
base.OnElementChanged(e);
var view = NativeView as UIWebView;
var swipeBackRecognizer = new UISwipeGestureRecognizer(HandleSwipeBack);
swipeBackRecognizer.Direction = UISwipeGestureRecognizerDirection.Right;
view.AddGestureRecognizer(swipeBackRecognizer);
}
void HandleSwipeBack()
{
(Element as WebView).GoBack();
}
}
This solution is fairly basic, it just does the GoBack() action without actually showing the swipe of the page like the normal Safari browser does. So you might have to add some more code if that's what you need.
Transitioning
You can make use of CoreAnimation for this. Use this as your HandleSwipeBack method:
void HandleSwipeBack()
{
CATransition animation = new CATransition();
animation.Type = CAAnimation.TransitionPush;
animation.Subtype = CAAnimation.TransitionFromLeft;
animation.Duration = 0.50;
animation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut);
(NativeView as UIWebView).Layer.AddAnimation(animation, CALayer.Transition);
(Element as WebView).GoBack();
}
This will not get you the exact same behaviour as the Safari browser, as it will not cancel the animation if you stop the swipe halfway for example. It is possible to do so, but it would require more code.
Extra information
You might also be able to draw some inspiration from this Pull Request for a SwipeGestureRecognizer, which was not included in Xamarin Forms because unit tests and UI tests were missing.
Unfortunately the default Xamarin Forms GestureRecognizers are limited to Tap, Pinch and Pan for the moment.
Can you try the following
NavigationPage.SetBackButtonTitle (this, "")
or alternatively you can add tool bar item and then try something like this
ToolbarItems.Add(new ToolbarItem("Back", "BackIcon.png", () => { _webview.GoBack(); }));

How can you play an AudioFile that will keep playing even if you navigate through pages in Xamarin Forms?

I am trying to make like my own music app. With my dependency service solution in iOS i can hear the mp3 just fine but once I navigate through other pages the file stops. So my question is, how can I make so the file keeps playing even if i navigate through different pages after i "play" it?
This is my code:
My button where I pick the "track".
async void PlayThisSongButton (object s, EventArgs e)
{
DependencyService.Get<IPlayerVoice>().PlayAudioFile("myfilename.mp3");
}
Interface:
public interface IPlayerVoice
{
void PlayAudioFile(string fileName);
}
Dependency service iOS:
[assembly: Xamarin.Forms.Dependency(typeof (VoicePlayer_iOS))]
namespace myProject.iOS
{
public class VoicePlayer_iOS : IPlayerVoice
{
AVAudioPlayer player;
public VoicePlayer_iOS()
{
}
public void PlayAudioFile(string fileName)
{
string sFilePath = NSBundle.MainBundle.PathForResource
(Path.GetFileNameWithoutExtension(fileName), Path.GetExtension(fileName));
var url = NSUrl.FromString(sFilePath);
var _player = AVAudioPlayer.FromUrl(url);
_player.FinishedPlaying += (object sender, AVStatusEventArgs e) =>
{
_player = null;
};
_player.Play();
}
}
}
So with this current code. I click on my button where i start the audiofile. I then navigate to a different page. The audiofile stops. Any idea how i can solve this?
I have never worked with audio in Xamarin Forms but looked into it once.
I think the comment from #yanyankelevich with a GitHub link would still stop the audio when the user navigates away from the page.
As #yanyankelevich suggested though, using a background service might be your best bet. Some links for that (there is a lot of code so I will not duplicate it in this post):
iOS AVAudioPlayer (specifically check out the PlayBackgroundMusic method and the other background related methods below that one)
Android Background Audio Streaming
For a super simple way of doing it (which I do not suggest and have not ever tried), have you tried just running your method within a Device.BeginInvokeOnMainThread() which will do a fire and forget?
Device.BeginInvokeOnMainThread(() => DependencyService.Get<IPlayerVoice>().PlayAudioFile("myfilename.mp3"));
I would say you need to Use MVVM or MVC and run the music on the main view.
this will allow you to browse the app, while your music still plays.
basically have the Main view and create a grid that you place your "browserView"
in, add all the normal code you would use for the app to the viewmodel of the browser.
then add the code for the music to the viewmodel of the mainview.
Just make sure to add the functionality to pause the music.

MVVMCross Calling function from the ViewModel to the View Xamarin

I want to show a popup when someone click on a button, the button is in a listview which is load from a webservice, how can I send a message to the view to call the AlertDialog function ?
I was maybe not very clear I paste my code bellow to see what I tried :
In my viewmodel :
public void editPost(Post item)
{
PostToEdit = item;
// Call the popup function
}
In my view :
public Dialog showEditPopup()
{
var customView = LayoutInflater.Inflate(Resource.Layout.EditDialog, null);
var builder = new AlertDialog.Builder(this);
builder.SetView(customView);
builder.SetPositiveButton("Save", SaveClicked);
builder.SetNegativeButton("Cancel", CancelClicked);
return builder.Create();
}
I tried to create an onclick function to initialize my AlertDialog in the View
var editButton = FindViewById<Button>(Resource.Id.editButton);
editButton.Click += delegate { ShowDialog(EditDialog); };
But the application crash because the posts aren't load at the time of the oncreate so the editButton is null and the event click cannot be set, so I want to create the popup in the viewmodel.
One way you can go about this is to set (from the view) an Action or Func callback on the VM, which will show the dialog, and it can be initiated from the VM.
The other option would be to have an interface defined by the VM which the View will implement (for example IViewInteraction which has a method like ShowDialog(string text).
I would suggest the first approach.
Probably there's going to be more than one place where you will need to show an alert dialog in your app.
In MvvmCross there's a plugin to show dialogs in all platforms. You implement calling it from view-model and it will work on all platforms.
https://github.com/brianchance/MvvmCross-UserInteraction
I suggest you add it by NuGet

Xamarin Forms Android System Exception "Android only allows one navigation page on screen at a time"

Recently i decided to give Xamarin Forms a try, so i started migrating my Windows Phone / Android Xamarin project, to the new "Forms" format.
After migrating two simple screens (views), the code runs just fine on my Windows Phone, but it crashes on the Android Platform. I am using two devices to test the code, with no emulator to avoid unecessary headaches.
The exception raised on the android platform is: System.Exception: Android only allows one navigation page on screen at a time.
The problem is not related to the "View" class itself, since it doesn`t happen once i set it as the "start screen" for the Android.
I'm afraid the problem is related to the fact that i am "Pushing" two screens via the "Navigation.PushAsync(...)" method provided by Xamarin.
Any ideas on how do actually fix it ?
EDIT:
Code Sample
Overview: The "MainView.cs" is just a content page with a label on the top, followed by two buttons alligned vertically.
How to reproduce the exception: Click on "Get All Users" button on the mainview. I have modified the code to push the same view once the button is clicked, instead of pushing another view (The view that has a listview of users, which i haven't included, just to make it simple). Once the button is clicked, it will navigate to a new instance of "MainView" by calling "Navigation.PushAsync(...)". This will trigger the exception after the screen is loaded.
You are nesting Navigation pages. This is not necessary. Try this instead:
public class ViewsHandler
{
public static Page GetMainPage()
{
return new MainView();
}
public static Page GetUsersListPage()
{
return new UsersListView ();
}
}
public class MainActivity : AndroidActivity
{
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
// Initializing Xamarin Form
Xamarin.Forms.Forms.Init (this, bundle);
// create a single NavigationPage wrapping your content
SetPage (new NavigationPage(ViewsHandler.GetMainPage()));
}
}

How do I handle Login/Logout in Caliburn.Micro?

I'm new to Caliburn.Micro and I'm wondering what is the best way to handle user Login/Logout cycles in my application. I saw some suggestions online to implement this using an empty Shell-View which switches between the LoginView and the main application view, each with a custom ViewModel of course.
I don't really like this solution, because for me these are 2 separate windows with very different properties (Title, Icon, Size) and it seems an unclean solution two change one window to look like the other. Another problem is, that the Login Window comes from an utility library which I don't control and which doesn't use Caliburn.Micro, it's a plain old Window which gives me an event when the user clicks "Login".
I also saw suggestions to display this Dialog in the Bootstrapper startup method, but the problem I see with that is that the user can choose to "Logout" of the application which should display the Login dialog again. It seems wrong to me to handle the switching between the Views in the Bootstrapper.
What I would like is to have some sort of ApplicationViewModel or ApplicationController which works like a Caliburn Conductor, but instead of switching between Views inside a Window, it should switch between the LoginWindow and the MainWindow and should also handle Closing of the whole application (which also requires a Logout). On Activation it would show the LoginWindow, handle the Login event and then switch to the Main Window (Shell). If the user chooses to "LogOut", the event should bubble up to the ApplicationViewModel/Controller again which would deactivate/close the MainWindow, perform the Logout and then show the LoginDialog again. Similar a Close event would do the Logout, but then Shutdown the whole application.
So my questions are:
What do you think about this solution and do you have another/better one?
How do I implement this? ;-)
Thanks a lot!
I think the solution to your problem is fairly easy.
In a nutshell you are creating one ViewModel as Shell which is represented with a Login Window when the application starts. If the user logs in successfully this window closes and the same instance of the viewModel is displayed in a Content Window. If the user is doing a logout, the Login Window is shown again.
First of all create an interface IShell which exposes two delegates LoginSuccessful and Logout
public interface IShell
{
Action LoginSuccessful { get; set; }
Action Logout { get; set; }
}
Next create a class ShellViewModel which implements IShell
public class ShellViewModel : Screen, IShell
{
public ShellViewModel()
{
LoginSuccessful = delegate { };
Logout = delegate { };
}
public Action LoginSuccessful { get; set; }
public Action Logout { get; set; }
public void DoLogin()
{
LoginSuccessful();
}
public void DoLogout()
{
Logout();
}
}
The methods DoLogin and DoLogout are Actions which can be bound to a Button or whatever control appropriate for you.
Next step is to override the OnStartupMethod in your Bootstrapper. This premises that you have an instance of the WindowManager and ShellViewModel exported by an IoC Framework of your choice.
protected override void OnStartup(object sender, StartupEventArgs e)
{
var windowManager = IoC.Get<IWindowManager>();
var viewModel = IoC.Get<IShell>();
viewModel.LoginSuccessful =
() => GuardCloseAndReopen("Content");
viewModel.Logout =
() => GuardCloseAndReopen("Login");
windowManager.ShowWindow(viewModel, "Login");
}
private void GuardCloseAndReopen(string shellViewMode)
{
var windowManager = IoC.Get<IWindowManager>();
var shellScreen = IoC.Get<IShell>() as Screen;
Application.ShutdownMode = ShutdownMode.OnExplicitShutdown;
shellScreen.TryClose();
Application.ShutdownMode = ShutdownMode.OnLastWindowClose;
windowManager.ShowWindow(shellScreen, shellViewMode);
}
The trick to this is: If the DoLogout method is called, the current window gets closed by calling TryClose on the ShellViewModel. At the same time you prevent the application from being shutdown by setting the Application.ShutdownMode to OnExplicitShutdown. Then using the windowmanager, you create another window in Login Mode by passing "Login" as Context information to the windowManager. This is actually the same ViewModel, however, with a different visual representation.
For Logout you are doing the same thing just around.
To get this working using Caliburn Conventions, you need a special project structure as seen here (and explained there):
Now I challenge you to take this code and create a little sample application. Create a Login View (which does Login with a Button or whatever) and create a Content View with a Logout Button using the LoginSuccessful/ Logout Methods.
This will solve your issue with a minimum of Code and classes. Hope this will be helpful to you.
I've had a go at creating something that basically works but probably needs a bit more work to be really usable. The fully comments and source can be found on this post Caliburn.Micro Login Window sample on my website.
I used the IEventAggregator of Caliburn.Micro to control the transition between the two windows. You get this code to open the login screen:
public void Handle(LoginEvent message)
{
LoginWindow loginWindow = new LoginWindow();
loginWindow.Login += new EventHandler<LoginEventArgs>(this.LoginWindow_Login);
loginWindow.Cancel += new EventHandler(LoginWindow_Cancel);
loginWindow.ShowDialog();
}
this same source is used for both the first time the app opens and when the Logout event is published. the Logout event looks like this:
public void Handle(LogoutEvent message)
{
Application.Current.ShutdownMode = ShutdownMode.OnExplicitShutdown;
message.Source.TryClose();
Application.Current.ShutdownMode = ShutdownMode.OnLastWindowClose;
this.events.Publish(new LoginEvent());
}
When a login is successful it uses this code to open the main window which is based on a ViewModel:
ContentViewModel viewModel;
viewModel = IoC.Get<ContentViewModel>();
viewModel.Username = e.Username;
this.windowManager.ShowWindow(viewModel);

Categories

Resources