Skip to content

Instantly share code, notes, and snippets.

@jcmm33
Created June 29, 2016 17:23
Show Gist options
  • Save jcmm33/9722d6ec1145437cd81fc7d66e6ea6e5 to your computer and use it in GitHub Desktop.
Save jcmm33/9722d6ec1145437cd81fc7d66e6ea6e5 to your computer and use it in GitHub Desktop.
Appcompat Fragments
using System;
using System.Collections.Generic;
using Android.Runtime;
using System.ComponentModel;
using System.Reactive.Subjects;
using System.Reactive;
using ReactiveUI;
namespace ReactiveUI.Framework.Android
{
/// <summary>
/// This is a Fragment that is both an Activity and has ReactiveObject powers
/// (i.e. you can call RaiseAndSetIfChanged)
/// </summary>
public class ReactiveAppCompatFragment<TViewModel> : ReactiveAppCompatFragment, IViewFor<TViewModel>, ICanActivate
where TViewModel : class
{
protected ReactiveAppCompatFragment() { }
protected ReactiveAppCompatFragment(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership)
{
}
/// <summary>
/// The underlying view model
/// </summary>
//private TViewModel _viewModel;
private HiddenReference<TViewModel> _viewModel = new HiddenReference<TViewModel>();
public TViewModel ViewModel
{
get { return this._viewModel.Value; }
set
{
this.RaiseAndSetIfChangedHiddenReference(ref this._viewModel, value);
}
}
object IViewFor.ViewModel {
get { return ViewModel; }
set { ViewModel = (TViewModel)value; }
}
public override void OnDestroyView()
{
// belt and braces, clear down the view model.
this.ViewModel = null;
base.OnDestroyView();
}
}
/// <summary>
/// This is a Fragment that is both an Activity and has ReactiveObject powers
/// (i.e. you can call RaiseAndSetIfChanged)
/// </summary>
public class ReactiveAppCompatFragment : global::Android.Support.V4.App.Fragment, IReactiveNotifyPropertyChanged<ReactiveAppCompatFragment>, IReactiveObject, IHandleObservableErrors
{
protected ReactiveAppCompatFragment() { }
protected ReactiveAppCompatFragment(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership)
{
}
public event PropertyChangingEventHandler PropertyChanging
{
add { PropertyChangingEventManager.AddHandler(this, value); }
remove { PropertyChangingEventManager.RemoveHandler(this, value); }
}
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args)
{
PropertyChangingEventManager.DeliverEvent(this, args);
}
public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged
{
add { PropertyChangedEventManager.AddHandler(this, value); }
remove { PropertyChangedEventManager.RemoveHandler(this, value); }
}
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args)
{
PropertyChangedEventManager.DeliverEvent(this, args);
}
/// <summary>
/// Represents an Observable that fires *before* a property is about to
/// be changed.
/// </summary>
public IObservable<IReactivePropertyChangedEventArgs<ReactiveAppCompatFragment>> Changing
{
get { return this.getChangingObservable(); }
}
/// <summary>
/// Represents an Observable that fires *after* a property has changed.
/// </summary>
public IObservable<IReactivePropertyChangedEventArgs<ReactiveAppCompatFragment>> Changed
{
get { return this.getChangedObservable(); }
}
/// <summary>
/// When this method is called, an object will not fire change
/// notifications (neither traditional nor Observable notifications)
/// until the return value is disposed.
/// </summary>
/// <returns>An object that, when disposed, reenables change
/// notifications.</returns>
public IDisposable SuppressChangeNotifications()
{
return this.suppressChangeNotifications();
}
public IObservable<Exception> ThrownExceptions { get { return this.getThrownExceptionsObservable(); } }
readonly Subject<Unit> activated = new Subject<Unit>();
public IObservable<Unit> Activated { get { return activated; } }
readonly Subject<Unit> deactivated = new Subject<Unit>();
public IObservable<Unit> Deactivated { get { return deactivated; } }
public override void OnPause()
{
base.OnPause();
deactivated.OnNext(Unit.Default);
}
public override void OnResume()
{
base.OnResume();
activated.OnNext(Unit.Default);
}
}
}
using System;
using System.Linq;
using System.Reactive.Disposables;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Android.OS;
using Android.Runtime;
using Android.Views;
using Common;
using Common.Logging;
using Newtonsoft.Json.Serialization;
using ReactiveUI.Framework.Scope;
using ReactiveUI.Framework.ViewModel;
namespace ReactiveUI.Framework.Android.Views
{
public abstract class RxAppCompatFragment<TViewModel> : ReactiveAppCompatFragment<TViewModel>,ILifetimeContainer,IRxLogPublisher where TViewModel:RxViewModel
{
/// <summary>
/// The layout resource Id to be used.
/// </summary>
private int _layoutResourceId;
private static readonly MethodInfo GetControlView;
private readonly HiddenReference<ILifetime> _hiddenLifetime = new HiddenReference<ILifetime>(new Lifetime());
public ILifetime Lifetime
{
get { return _hiddenLifetime.Value; }
set { _hiddenLifetime.Value = value; }
}
protected RxAppCompatFragment(int layoutResourceId)
{
_layoutResourceId = layoutResourceId;
Setup();
}
protected RxAppCompatFragment(IntPtr handle, JniHandleOwnership ownership) : base(handle, ownership)
{
Setup();
}
protected RxAppCompatFragment()
{
Setup();
}
private void Setup()
{
// indicate you want the fragment instance to be retained...
this.RetainInstance = true;
this.WhenActivated(d =>
{
try
{
var activatedLifetime = Lifetime.BeginLifetimeScope();
this.OnViewActivated(activatedLifetime);
d(Disposable.Create(OnViewDeActivated));
d(activatedLifetime);
}
catch (Exception exception)
{
this.Log().Error(exception).Throw(exception);
}
});
}
static RxAppCompatFragment()
{
var type = typeof(RxAppCompatFragment<TViewModel>);
GetControlView = type.GetMethod("GetControl", new[] { typeof(string) });
}
public T GetControl<T>([CallerMemberName] string propertyName = null) where T : global::Android.Views.View
{
return (T)_view.GetControl<T>(propertyName);
}
public override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
if (savedInstanceState == null)
{
OnFirstCreate();
}
else
{
OnRestore(savedInstanceState);
}
}
private ILifetime _viewLifetime;
private global::Android.Views.View _view;
public override global::Android.Views.View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
_view = CreateView(inflater, container);
_viewLifetime = Lifetime.BeginLifetimeScope();
this.WireUpControls();
OnViewCreated(savedInstanceState);
CreateBindings(container,_viewLifetime);
return _view;
}
public override void OnDestroyView()
{
_viewLifetime?.Dispose();
_viewLifetime = null;
_view = null;
base.OnDestroyView();
}
public virtual void OnFirstCreate()
{
}
public virtual void OnRestore(Bundle savedInstanceState)
{
OnLoad(savedInstanceState);
}
public override void OnStop()
{
base.OnStop();
}
public override void OnSaveInstanceState(Bundle outState)
{
OnSave(outState);
base.OnSaveInstanceState(outState);
}
public virtual void OnLoad(Bundle savedState)
{
var contractResolver = ApplicationContext.GetService<IContractResolver>();
this.ViewModel = this.LoadInstance<TViewModel>(contractResolver, savedState);
}
public virtual void OnSave(Bundle outState)
{
this.SaveInstance(outState);
}
/// <summary>
/// Default Create Layout
/// </summary>
/// <remarks>
/// Looks for RxLayoutAttribute on the class if we don't have a value</remarks>
public virtual global::Android.Views.View CreateView(LayoutInflater inflater,ViewGroup container)
{
if (_layoutResourceId == 0)
{
_layoutResourceId = this.GetContentLayoutResourceId();
}
if (_layoutResourceId <= 0)
{
this.Log().Error("Missing Layout Resource Id").Throw(l => new InvalidOperationException(l.Instance.Message));
}
var view = inflater.Inflate(_layoutResourceId, null);
return view;
}
/// <summary>
/// The view has been created. Typically the layout is constructed from here
/// </summary>
/// <param name="savedInstanceState"></param>
public virtual void OnViewCreated(Bundle savedInstanceState)
{
}
public virtual void CreateBindings(ViewGroup container,ILifetime viewLifetime)
{
}
/// <summary>
/// Called upon Fragment activation.
/// </summary>
public virtual void OnViewActivated(ILifetime lifetime)
{
}
// ReSharper disable once CSharpWarnings::CS1998
/// <summary>
/// Called when the fragment is deactivated.
/// </summary>
/// <returns></returns>
public virtual void OnViewDeActivated()
{
// need to let the presenter know that the view is going...
// and finally return
}
/// <summary>
/// Wireup the controls.
/// </summary>
public void WireUpControls()
{
var members =
this.GetType()
.GetRuntimeProperties()
.Where(m => m.PropertyType.IsSubclassOf(typeof(global::Android.Views.View)));
members.ToList().ForEach(m =>
{
try
{
// Find the android control with the same name
var view = this.GetControlInternal(_view, m.PropertyType, m.Name);
// Set the activity field's value to the view with that identifier
m.SetValue(this, view);
}
catch (Exception ex)
{
throw new MissingFieldException("Failed to wire up the Property "
+ m.Name +
" to a View in your layout with a corresponding identifier", ex);
}
});
}
private global::Android.Views.View GetControlInternal(global::Android.Views.View parent, Type viewType, string name)
{
var mi = GetControlView.MakeGenericMethod(new[] { viewType });
return (global::Android.Views.View)mi.Invoke(this, new object[] { name });
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment