How stop playing AVPlayer on page leave Xamarin Forms - xamarin

I have AVPlayer and when i move to next page, player continues playing and if i add observers they crash if i don't dispose them but i am unable to find a way to handle dispose because ContentView doesn't tell you when it is in background.
Please help ?

In Xamarin Forms there is no way for a ContentView to find out when it's hosting page is disappearing, unless it gets some help from the Page itself. So how I've achieved this previously is as follows:
Step 1) Define an OnDisappearing Method in your ContentView
In your ContentView define a method called OnDisappearing and inside it, do whatever you need to when the view disappears - in your case it sounds like you need to remove your observers and dispose your player. So it would look something like this:
public void OnDisappearing()
{
_playerPositionChangedObserver?.Dispose();
_player?.Dispose();
}
Defining this method in itself won't do anything, you need to actually call it from somewhere - that's where the page comes in...
Step 2) Override the OnDisappearing method in your page
The page will be told when it's appearing or disappearing and you can use that to then forward on to your other ContentView. In the example below MyPage is overriding the OnDisappearing method and then calling that method that I defined on my ContentView in step 1.
public class MyPage : ContentPage
{
public MyPage()
{
InitializeComponent();
}
/// <summary>
/// Performs page clean-up.
/// </summary>
protected override void OnDisappearing()
{
base.OnDisappearing();
contentView.OnDisappearing();
}
}

Related

How can I set up my application main page (shell) from the OnAppearing of a login page with Xamarin Forms?

I have an application that I added a launch page to in the iOS and Android code. However when the app starts there is still quite a long delay while it fetches data. At this time there's a blank screen where I assume the app is still setting up the constructor.
I am trying to have an in-between page where that appears that loads the data. Not sure if this is the best way to do this but so far it's all that I have.
Here's the code that I have so far:
public App()
{
InitializeComponent;
MainPage = new NavigationPage(new Test.LoginPage())
{
};
}
My Test.LoginPage is a simple empty Xaml page with this C# back end:
public partial class LoginPage : ContentPage
{
public LoginPage()
{
InitializeComponent();
}
protected async override void OnAppearing()
{
await LongRunningTask();
App.MainPage = new AppShell(); // I want to start a shell app
}
}
public partial class AppShell : Shell
{
public AppShell()
{
Routing.RegisterRoute("HomeTab/QHPage", typeof(QHPage));
// etc
But the code has issues in that first of all I am not sure I am doing it correctly and secondly it says an object reference is required for App.MainPage.
Can anyone point me in the right direction and suggest how I could display this intermediate page and then display the real app pages?
Note that at some point I would also like to have a button on the login page that when clicked takes me to the app. But at this time I just want to get even the most simple version working so I am looking for some advice with that.
The easy way is to set the AppShell as MainPage. The code below works for me.
Application.Current.MainPage = new AppShell();

Xamarin forms Navigation issue. Popped page stays active

I have a page "PageA" which listens to a Changed event on a global object.
The page is added in the navigation stack like this:
await Navigation.PushAsync(new PageA());
The page is removed by clicking the back button in the navigation bar.
When I do the same x times, I have x times PageA instances listening to the Changed event. So PageA is created over and over again, but is never really removed.
How do I really get rid of the PageA when clicking the back button?
Example of what you are doing
public partial class MyPage : ContentPage
{
MyPage()
{
this.SomeEvent += SomeEventBeyondTheScopeOfThisPage();
}
protected override void OnDisappearing()
{
//You should add this
this.SomeEvent -= SomeEventBeyondTheScopeOfThisPage();
}
}
Which even if the page is popped from your navigation stack does not remove the reference from MyPage.SomeEvent to SomeEventBeyondTheScopeOfThisPage. So when the Garbage Collector comes along this page is going to stay and this event is going to continue to be listened for.
Just detach the event in OnDisappearing for the simple answer. You dont dispose the page you just detach all references and events outside of its scope. A better idea would be to make your code more modular and not worry about detaching the event. If the event source were coming from within the page itself it would not need to be detached.
public partial class MyPage : ContentPage
{
MyPage()
{
this.SomeEvent += SomeEventWithinPagesScope();
}
private Task SomeEventWithinPagesScope()
{
//My cool event goes here
}
}
If it does have to be global another option would be Messaging Center. I do not believe you have to detach those listeners but I could be wrong.
https://developer.xamarin.com/guides/xamarin-forms/messaging-center/
another reason: NavigationPage.CurrentNavigationTask.Result still refer to the popped page instance. that is why GC will not collect it. unless you navigate to other pages.

Handling secondary popup controller events from main controller JavaFX

I have a main controller which handles my main.fxml and a second controller which handles my popup.fxml
When a button is pressed from the main controller, the popup windows appears. In the popup window you add players. The players are added by textfield to an array and must be sent back to main controller. I have a button called "btnApply" in my popup controller, when that is pressed I want to close the popup window and handle the array from my main controller class. I only want my main controller class to be aware of the popup.
This is how I am creating a popup from main controller:
button.setOnAction(e -> newWindow());
public void newWindow(){
try{
FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("popup.fxml"));
Parent popupRoot = fxmlLoader.load();
Stage playerStage = new Stage();
playerStage.setTitle("Player");
playerStage.setScene(new Scene(popupRoot, 720, 600));
playerStage.show();
}catch(Exception e) {
e.printStackTrace();
}
}
Now the question is how to I get the event or the object. When I created the popup window without using FXML (created the GUI manually), it was easy because I just made an object of the Class Popup and had a getButton() and getArray(). In my main controller class I had a Popup popup = new Popup(); then I had a method where I handle the button from my popup class popup.getButton().setOnAction(e -> addPlayers());
But this is not possible using fxml. I cant seem to get the object that is running. If I were to create a Popup popup I will just get a new event not the one that is being ran.
The way to do this most similar to your previous approach would be adding the getButton() method to to your controller class and get the controller class from the FXMLLoader:
Parent popupRoot = fxmlLoader.load();
MyController controller = fxmlLoader.<MyController>getController();
controller.getButton()...
Alternatives
However I recommend a different approach to passing the data, since this way you limit yourself to a single button in the popup as the only way to submit the players. I'd rather do this by "notifying" the class creating the popup, i.e:
Implement this interface in the calling class
public interface PlayerContainer {
void addPlayers(Player[] players);
}
and add this to the controller:
private PlayerContainer playerContainer;
public void setPlayerContainer(PlayerContainer playerContainer) {
this.playerContainer = playerContainer;
}
And pass the calling class to the controller directly after loading the popup content:
Parent popupRoot = fxmlLoader.load();
MyController controller = fxmlLoader.<MyController>getController();
controller.setPlayerContainer(this);
and when the user submits the player data, simply call
this.playerContainer.addPlayers(playerData);
in addition to closing the window. Passing a ObservableList<Player> to the controller class instead and adding all players to this list would work too, if you handle changes to the list appropriately in the calling class.
Take a look at jewelsea's answer to "Passing Parameters JavaFX FXML". This lists some alternative ways to can pass objects to the controller of the fxml. The Setting a Controller on the FXMLLoader approach could be easy to implement, e.g. if you use a inner class of the calling class as the controller class. This way it's harder to reuse the popup than with the approach described above however...

XamarinForms AppCompat OnOptionsItemSelected

I have recently updated xamarin forms to 1.5.1-pre1 so that I can use the beautiful AppCompat themes. It works and looks very nice.
I do have one problem, in my old FormsApplicationActivity I used to override the OnOptionsItemSelected method to intercept when the user was clicking on the back arrow icon and do some viewmodel cleanup. Apparently this method is not being called after using the FormsAppCompatActivity.
How can I intercept the "soft" back button press (toolbar icon not hard back button) ?
I also tried to override the Xamarin.Forms.Platform.Android.AppCompat.NavigationPageRenderer but i can't seem to override it :(
Does anyone have a clue how I can intercept this?
You can add the following to your custom renderer. You can either user current activity plugin or cast your context to activity.
var toolbar = CrossCurrentActivity.Current?.Activity?.FindViewById<Toolbar>(Resource.Id.toolbar);
toolbar.NavigationClick += ToolbarNavigationClick;
Have a look at the last two lines of the OnCreate() After adding them OnOptionsItemSelected was called as expected).
https://raw.githubusercontent.com/UdaraAlwis/Xamarin-Playground/master/XFNavBarBackBtnClickOverride/XFNavBarBackBtnClickOverride/XFNavBarBackBtnClickOverride.Droid/MainActivity.cs
Toolbar toolbar = this.FindViewById<Toolbar>(Resource.Id.toolbar);
SetSupportActionBar(toolbar);
In Xamarin.Forms there's a better way to intercept the NavigationBar back button pressed and the hardware back button pressed, which is creating your own NavigationRenderer and overriding the method OnPopViewAsync:
[assembly: ExportRenderer(typeof(NavigationPage), typeof(CustomNavigationRenderer))]
namespace YourApp.Droid
{
public class CustomNavigationRenderer : NavigationPageRenderer
{
public CustomNavigationRenderer(Context context) : base(context)
{
}
protected override async Task<bool> OnPopViewAsync(Page page, bool animated)
{
// Write your code here
}
}
}
Hope this helps

Update the view although the model hasn't changed

My app updates the view in response to events dispatched by the model. But what if the model hasn't changed, but I still need to update the view. For example, I've closed and reopened a pop-up. The data to be displayed hasn't changed but the pop-up mediator and the view have to be recreated. My current solution is to force initialization in the mediator's onRegister() method like this:
// Inside of PopUpMediator.as
[Inject]
public var popUpModel:IPopUpModel;
[Inject]
public var popUpView:PopUpView;
override public function onRegister()
{
// Force initialization if the model hasn't changed
popUpView.foo = popUpModel.foo;
// Event based initialization
addContextListener(PopUpModelEvent_foo.CHANGE, foo_changeHandler);
}
Injecting models into mediators isn't a good idea, so I'm wondering What is the best way to init the view when its model hasn't changed?
Well,
I supose you have View1 where you have popup button.
View2 is your popus.
so when View1 button is clicked, you dispatch an event from main mediator that goes to popupCommand where you add popup to contextView, or where you remove it.
You can also have one state inside a model, that will say popupVisible and when you change that property you dispatch a event that is listened in the main mediator and that adds or removes the popup. In that case command would alter the model property instead of adding popup directly to contextView.
Third way is to add popup manually inside the view, and since the stage is being listened to by robotlegs, popup will be mediated automatically.
I've decided to add an event called PopUpViewInitEvent. A command will check if the model was updated while the pop-up was closed. If not it will reinitialize the view by dispatching the PopUpViewInitEvent. The event will contain all the data required to initialize the view. This way I won't have to inject models into my mediator.
[Inject]
public var popUpView:PopUpView;
override public function onRegister()
{
// Batch initialization
addContextListener(PopUpViewInitEvent.INIT, batchInit);
// Gradual initialization
addContextListener(PopUpModelEvent_foo.CHANGE, foo_changeHandler);
addContextListener(PopUpModelEvent_bar.CHANGE, bar_changeHandler);
}
protected function batchInit(event:PopUpViewInitEvent)
{
popUpView.foo = event.foo;
popUpView.bar = event.bar;
}

Resources