Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@
<PackageVersion Include="xunit.runner.console" Version="2.9.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
<PackageVersion Include="Xunit.StaFact" Version="1.1.11" />
<PackageVersion Include="Xamarin.AndroidX.Core" Version="1.13.1.4" />
<PackageVersion Include="Xamarin.AndroidX.Preference" Version="1.2.1.9" />
<PackageVersion Include="Xamarin.AndroidX.Legacy.Support.Core.UI" Version="1.0.0.29" />
<PackageVersion Include="Xamarin.Google.Android.Material" Version="1.11.0.2" />
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.8.4.1" />
</ItemGroup>
<ItemGroup Condition="'$(UseMaui)' != 'true'">
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.5.240802000" />
Expand Down
49 changes: 49 additions & 0 deletions src/ReactiveUI.AndroidX/ControlFetcherMixin.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Android.Views;

using static ReactiveUI.ControlFetcherMixin;

using Fragment = AndroidX.Fragment.App.Fragment;

namespace ReactiveUI.AndroidX;

/// <summary>
/// ControlFetcherMixin helps you automatically wire-up Activities and
/// Fragments via property names, similar to Butter Knife, as well as allows
/// you to fetch controls manually.
/// </summary>
public static class ControlFetcherMixin
{
/// <summary>
/// Wires a control to a property.
/// This should be called in the Fragment's OnCreateView, with the newly inflated layout.
/// </summary>
/// <param name="fragment">The fragment.</param>
/// <param name="inflatedView">The inflated view.</param>
/// <param name="resolveMembers">The resolve members.</param>
public static void WireUpControls(this Fragment fragment, View inflatedView, ResolveStrategy resolveMembers = ResolveStrategy.Implicit)
{
ArgumentNullException.ThrowIfNull(fragment);

foreach (var member in fragment.GetWireUpMembers(resolveMembers))
{
try
{
// Find the android control with the same name from the view
var view = inflatedView.GetControl(fragment.GetType().Assembly, member.GetResourceName());

// Set the activity field's value to the view with that identifier
member.SetValue(fragment, view);
}
catch (Exception ex)
{
throw new
MissingFieldException("Failed to wire up the Property " + member.Name + " to a View in your layout with a corresponding identifier", ex);
}
}
}
}
16 changes: 16 additions & 0 deletions src/ReactiveUI.AndroidX/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

global using global::Splat;
global using global::System;
global using global::System.Collections.Generic;
global using global::System.ComponentModel;
global using global::System.Diagnostics.CodeAnalysis;
global using global::System.Linq;
global using global::System.Reactive;
global using global::System.Reactive.Linq;
global using global::System.Reactive.Subjects;
global using global::System.Reactive.Threading.Tasks;
global using global::System.Threading.Tasks;
161 changes: 161 additions & 0 deletions src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

using Android.App;
using Android.Content;
using Android.Runtime;
using AndroidX.AppCompat.App;

namespace ReactiveUI.AndroidX;

/// <summary>
/// This is an Activity that is both an Activity and has ReactiveObject powers
/// (i.e. you can call RaiseAndSetIfChanged).
/// </summary>
public class ReactiveAppCompatActivity : AppCompatActivity, IReactiveObject, IReactiveNotifyPropertyChanged<ReactiveAppCompatActivity>, IHandleObservableErrors
{
private readonly Subject<Unit> _activated = new();
private readonly Subject<Unit> _deactivated = new();
private readonly Subject<(int requestCode, Result result, Intent? intent)> _activityResult = new();

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveAppCompatActivity" /> class.
/// </summary>
protected ReactiveAppCompatActivity()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveAppCompatActivity" /> class.
/// </summary>
/// <param name="handle">The handle.</param>
/// <param name="ownership">The ownership.</param>
protected ReactiveAppCompatActivity(in IntPtr handle, JniHandleOwnership ownership)
: base(handle, ownership)
{
}

/// <inheritdoc/>
public event PropertyChangingEventHandler? PropertyChanging;

/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged;

/// <inheritdoc/>
public IObservable<IReactivePropertyChangedEventArgs<ReactiveAppCompatActivity>> Changing => this.GetChangingObservable();

/// <inheritdoc/>
public IObservable<IReactivePropertyChangedEventArgs<ReactiveAppCompatActivity>> Changed => this.GetChangedObservable();

/// <inheritdoc/>
public IObservable<Exception> ThrownExceptions => this.GetThrownExceptionsObservable();

/// <summary>
/// Gets a signal when activated.
/// </summary>
/// <value>
/// The activated.
/// </value>
public IObservable<Unit> Activated => _activated.AsObservable();

/// <summary>
/// Gets a signal when deactivated.
/// </summary>
/// <value>
/// The deactivated.
/// </value>
public IObservable<Unit> Deactivated => _deactivated.AsObservable();

/// <summary>
/// Gets the activity result.
/// </summary>
/// <value>
/// The activity result.
/// </value>
public IObservable<(int requestCode, Result result, Intent? intent)> ActivityResult => _activityResult.AsObservable();

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);

/// <inheritdoc/>
public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);

/// <summary>
/// Starts the activity for result asynchronously.
/// </summary>
/// <param name="intent">The intent.</param>
/// <param name="requestCode">The request code.</param>
/// <returns>A task with the result and intent.</returns>
public Task<(Result result, Intent? intent)> StartActivityForResultAsync(Intent intent, int requestCode)
{
// NB: It's important that we set up the subscription *before* we
// call ActivityForResult
var ret = ActivityResult
.Where(x => x.requestCode == requestCode)
.Select(x => (x.result, x.intent))
.FirstAsync()
.ToTask();

StartActivityForResult(intent, requestCode);
return ret;
}

/// <summary>
/// Starts the activity for result asynchronously.
/// </summary>
/// <param name="type">The type.</param>
/// <param name="requestCode">The request code.</param>
/// <returns>A task with the result and intent.</returns>
public Task<(Result result, Intent? intent)> StartActivityForResultAsync(Type type, int requestCode)
{
// NB: It's important that we set up the subscription *before* we
// call ActivityForResult
var ret = ActivityResult
.Where(x => x.requestCode == requestCode)
.Select(x => (x.result, x.intent))
.FirstAsync()
.ToTask();

StartActivityForResult(type, requestCode);
return ret;
}

/// <inheritdoc/>
protected override void OnPause()
{
base.OnPause();
_deactivated.OnNext(Unit.Default);
}

/// <inheritdoc/>
protected override void OnResume()
{
base.OnResume();
_activated.OnNext(Unit.Default);
}

/// <inheritdoc/>
protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
{
base.OnActivityResult(requestCode, resultCode, data);
_activityResult.OnNext((requestCode, resultCode, data));
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
_activated.Dispose();
_deactivated.Dispose();
_activityResult.Dispose();
}

base.Dispose(disposing);
}
}
38 changes: 38 additions & 0 deletions src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveUI.AndroidX;

/// <summary>
/// This is an Activity that is both an Activity and has ReactiveObject powers
/// (i.e. you can call RaiseAndSetIfChanged).
/// </summary>
/// <typeparam name="TViewModel">The view model type.</typeparam>
public class ReactiveAppCompatActivity<TViewModel> : ReactiveAppCompatActivity, IViewFor<TViewModel>, ICanActivate
where TViewModel : class
{
private TViewModel? _viewModel;

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveAppCompatActivity{TViewModel}"/> class.
/// </summary>
protected ReactiveAppCompatActivity()
{
}

/// <inheritdoc/>
public TViewModel? ViewModel
{
get => _viewModel;
set => this.RaiseAndSetIfChanged(ref _viewModel, value);
}

/// <inheritdoc/>
object? IViewFor.ViewModel
{
get => _viewModel;
set => _viewModel = (TViewModel?)value;
}
}
83 changes: 83 additions & 0 deletions src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for full license information.

namespace ReactiveUI.AndroidX;

/// <summary>
/// This is a Fragment that is both an Activity and has ReactiveObject powers
/// (i.e. you can call RaiseAndSetIfChanged).
/// </summary>
public class ReactiveDialogFragment : global::AndroidX.Fragment.App.DialogFragment, IReactiveNotifyPropertyChanged<ReactiveDialogFragment>, IReactiveObject, IHandleObservableErrors
{
private readonly Subject<Unit> _activated = new();
private readonly Subject<Unit> _deactivated = new();

/// <summary>
/// Initializes a new instance of the <see cref="ReactiveDialogFragment"/> class.
/// </summary>
protected ReactiveDialogFragment()
{
}

/// <inheritdoc/>
public event PropertyChangingEventHandler? PropertyChanging;

/// <inheritdoc/>
public event PropertyChangedEventHandler? PropertyChanged;

/// <inheritdoc/>
public IObservable<Exception> ThrownExceptions => this.GetThrownExceptionsObservable();

/// <summary>
/// Gets a observable that signals when the fragment is activated.
/// </summary>
public IObservable<Unit> Activated => _activated.AsObservable();

/// <summary>
/// Gets a observable that signals when the fragment is deactivated.
/// </summary>
public IObservable<Unit> Deactivated => _deactivated.AsObservable();

/// <inheritdoc />
public IObservable<IReactivePropertyChangedEventArgs<ReactiveDialogFragment>> Changing => this.GetChangingObservable();

/// <inheritdoc/>
public IObservable<IReactivePropertyChangedEventArgs<ReactiveDialogFragment>> Changed => this.GetChangedObservable();

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);

/// <inheritdoc/>
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);

/// <inheritdoc />
public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);

/// <inheritdoc/>
public override void OnPause()
{
base.OnPause();
_deactivated.OnNext(Unit.Default);
}

/// <inheritdoc/>
public override void OnResume()
{
base.OnResume();
_activated.OnNext(Unit.Default);
}

/// <inheritdoc/>
protected override void Dispose(bool disposing)
{
if (disposing)
{
_activated.Dispose();
_deactivated.Dispose();
}

base.Dispose(disposing);
}
}
Loading
Loading