Skip to content

Commit de5f3fe

Browse files
authored
Feature Add AndroidX with new packages (#3889)
<!-- Please be sure to read the [Contribute](https://github.com/reactiveui/reactiveui#contribute) section of the README --> **What kind of change does this PR introduce?** <!-- Bug fix, feature, docs update, ... --> Feature **What is the current behavior?** <!-- You can also link to an open issue here. --> #3851 **What is the new behavior?** <!-- If this is a feature change --> Functionality ported from the Mono Android TFM version that went out of support. Only supports Net 8.0 plus with the latest packages released on 29th August 2024 **What might this PR break?** No support for older targets if upgrading from the legacy package. **Please check if the PR fulfills these requirements** - [ ] Tests for the changes have been added (for bug fixes / features) - [ ] Docs have been added / updated (for bug fixes / features) **Other information**:
1 parent 60b9d48 commit de5f3fe

24 files changed

+1343
-4
lines changed

src/Directory.Packages.props

+5
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@
3838
<PackageVersion Include="xunit.runner.console" Version="2.9.0" />
3939
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" />
4040
<PackageVersion Include="Xunit.StaFact" Version="1.1.11" />
41+
<PackageVersion Include="Xamarin.AndroidX.Core" Version="1.13.1.4" />
42+
<PackageVersion Include="Xamarin.AndroidX.Preference" Version="1.2.1.9" />
43+
<PackageVersion Include="Xamarin.AndroidX.Legacy.Support.Core.UI" Version="1.0.0.29" />
44+
<PackageVersion Include="Xamarin.Google.Android.Material" Version="1.11.0.2" />
45+
<PackageVersion Include="Xamarin.AndroidX.Lifecycle.LiveData" Version="2.8.4.1" />
4146
</ItemGroup>
4247
<ItemGroup Condition="'$(UseMaui)' != 'true'">
4348
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.5.240802000" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using Android.Views;
7+
8+
using static ReactiveUI.ControlFetcherMixin;
9+
10+
using Fragment = AndroidX.Fragment.App.Fragment;
11+
12+
namespace ReactiveUI.AndroidX;
13+
14+
/// <summary>
15+
/// ControlFetcherMixin helps you automatically wire-up Activities and
16+
/// Fragments via property names, similar to Butter Knife, as well as allows
17+
/// you to fetch controls manually.
18+
/// </summary>
19+
public static class ControlFetcherMixin
20+
{
21+
/// <summary>
22+
/// Wires a control to a property.
23+
/// This should be called in the Fragment's OnCreateView, with the newly inflated layout.
24+
/// </summary>
25+
/// <param name="fragment">The fragment.</param>
26+
/// <param name="inflatedView">The inflated view.</param>
27+
/// <param name="resolveMembers">The resolve members.</param>
28+
public static void WireUpControls(this Fragment fragment, View inflatedView, ResolveStrategy resolveMembers = ResolveStrategy.Implicit)
29+
{
30+
ArgumentNullException.ThrowIfNull(fragment);
31+
32+
foreach (var member in fragment.GetWireUpMembers(resolveMembers))
33+
{
34+
try
35+
{
36+
// Find the android control with the same name from the view
37+
var view = inflatedView.GetControl(fragment.GetType().Assembly, member.GetResourceName());
38+
39+
// Set the activity field's value to the view with that identifier
40+
member.SetValue(fragment, view);
41+
}
42+
catch (Exception ex)
43+
{
44+
throw new
45+
MissingFieldException("Failed to wire up the Property " + member.Name + " to a View in your layout with a corresponding identifier", ex);
46+
}
47+
}
48+
}
49+
}
+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
global using global::Splat;
7+
global using global::System;
8+
global using global::System.Collections.Generic;
9+
global using global::System.ComponentModel;
10+
global using global::System.Diagnostics.CodeAnalysis;
11+
global using global::System.Linq;
12+
global using global::System.Reactive;
13+
global using global::System.Reactive.Linq;
14+
global using global::System.Reactive.Subjects;
15+
global using global::System.Reactive.Threading.Tasks;
16+
global using global::System.Threading.Tasks;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
using Android.App;
7+
using Android.Content;
8+
using Android.Runtime;
9+
using AndroidX.AppCompat.App;
10+
11+
namespace ReactiveUI.AndroidX;
12+
13+
/// <summary>
14+
/// This is an Activity that is both an Activity and has ReactiveObject powers
15+
/// (i.e. you can call RaiseAndSetIfChanged).
16+
/// </summary>
17+
public class ReactiveAppCompatActivity : AppCompatActivity, IReactiveObject, IReactiveNotifyPropertyChanged<ReactiveAppCompatActivity>, IHandleObservableErrors
18+
{
19+
private readonly Subject<Unit> _activated = new();
20+
private readonly Subject<Unit> _deactivated = new();
21+
private readonly Subject<(int requestCode, Result result, Intent? intent)> _activityResult = new();
22+
23+
/// <summary>
24+
/// Initializes a new instance of the <see cref="ReactiveAppCompatActivity" /> class.
25+
/// </summary>
26+
protected ReactiveAppCompatActivity()
27+
{
28+
}
29+
30+
/// <summary>
31+
/// Initializes a new instance of the <see cref="ReactiveAppCompatActivity" /> class.
32+
/// </summary>
33+
/// <param name="handle">The handle.</param>
34+
/// <param name="ownership">The ownership.</param>
35+
protected ReactiveAppCompatActivity(in IntPtr handle, JniHandleOwnership ownership)
36+
: base(handle, ownership)
37+
{
38+
}
39+
40+
/// <inheritdoc/>
41+
public event PropertyChangingEventHandler? PropertyChanging;
42+
43+
/// <inheritdoc/>
44+
public event PropertyChangedEventHandler? PropertyChanged;
45+
46+
/// <inheritdoc/>
47+
public IObservable<IReactivePropertyChangedEventArgs<ReactiveAppCompatActivity>> Changing => this.GetChangingObservable();
48+
49+
/// <inheritdoc/>
50+
public IObservable<IReactivePropertyChangedEventArgs<ReactiveAppCompatActivity>> Changed => this.GetChangedObservable();
51+
52+
/// <inheritdoc/>
53+
public IObservable<Exception> ThrownExceptions => this.GetThrownExceptionsObservable();
54+
55+
/// <summary>
56+
/// Gets a signal when activated.
57+
/// </summary>
58+
/// <value>
59+
/// The activated.
60+
/// </value>
61+
public IObservable<Unit> Activated => _activated.AsObservable();
62+
63+
/// <summary>
64+
/// Gets a signal when deactivated.
65+
/// </summary>
66+
/// <value>
67+
/// The deactivated.
68+
/// </value>
69+
public IObservable<Unit> Deactivated => _deactivated.AsObservable();
70+
71+
/// <summary>
72+
/// Gets the activity result.
73+
/// </summary>
74+
/// <value>
75+
/// The activity result.
76+
/// </value>
77+
public IObservable<(int requestCode, Result result, Intent? intent)> ActivityResult => _activityResult.AsObservable();
78+
79+
/// <inheritdoc/>
80+
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
81+
82+
/// <inheritdoc/>
83+
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
84+
85+
/// <inheritdoc/>
86+
public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
87+
88+
/// <summary>
89+
/// Starts the activity for result asynchronously.
90+
/// </summary>
91+
/// <param name="intent">The intent.</param>
92+
/// <param name="requestCode">The request code.</param>
93+
/// <returns>A task with the result and intent.</returns>
94+
public Task<(Result result, Intent? intent)> StartActivityForResultAsync(Intent intent, int requestCode)
95+
{
96+
// NB: It's important that we set up the subscription *before* we
97+
// call ActivityForResult
98+
var ret = ActivityResult
99+
.Where(x => x.requestCode == requestCode)
100+
.Select(x => (x.result, x.intent))
101+
.FirstAsync()
102+
.ToTask();
103+
104+
StartActivityForResult(intent, requestCode);
105+
return ret;
106+
}
107+
108+
/// <summary>
109+
/// Starts the activity for result asynchronously.
110+
/// </summary>
111+
/// <param name="type">The type.</param>
112+
/// <param name="requestCode">The request code.</param>
113+
/// <returns>A task with the result and intent.</returns>
114+
public Task<(Result result, Intent? intent)> StartActivityForResultAsync(Type type, int requestCode)
115+
{
116+
// NB: It's important that we set up the subscription *before* we
117+
// call ActivityForResult
118+
var ret = ActivityResult
119+
.Where(x => x.requestCode == requestCode)
120+
.Select(x => (x.result, x.intent))
121+
.FirstAsync()
122+
.ToTask();
123+
124+
StartActivityForResult(type, requestCode);
125+
return ret;
126+
}
127+
128+
/// <inheritdoc/>
129+
protected override void OnPause()
130+
{
131+
base.OnPause();
132+
_deactivated.OnNext(Unit.Default);
133+
}
134+
135+
/// <inheritdoc/>
136+
protected override void OnResume()
137+
{
138+
base.OnResume();
139+
_activated.OnNext(Unit.Default);
140+
}
141+
142+
/// <inheritdoc/>
143+
protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
144+
{
145+
base.OnActivityResult(requestCode, resultCode, data);
146+
_activityResult.OnNext((requestCode, resultCode, data));
147+
}
148+
149+
/// <inheritdoc/>
150+
protected override void Dispose(bool disposing)
151+
{
152+
if (disposing)
153+
{
154+
_activated.Dispose();
155+
_deactivated.Dispose();
156+
_activityResult.Dispose();
157+
}
158+
159+
base.Dispose(disposing);
160+
}
161+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
namespace ReactiveUI.AndroidX;
7+
8+
/// <summary>
9+
/// This is an Activity that is both an Activity and has ReactiveObject powers
10+
/// (i.e. you can call RaiseAndSetIfChanged).
11+
/// </summary>
12+
/// <typeparam name="TViewModel">The view model type.</typeparam>
13+
public class ReactiveAppCompatActivity<TViewModel> : ReactiveAppCompatActivity, IViewFor<TViewModel>, ICanActivate
14+
where TViewModel : class
15+
{
16+
private TViewModel? _viewModel;
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref="ReactiveAppCompatActivity{TViewModel}"/> class.
20+
/// </summary>
21+
protected ReactiveAppCompatActivity()
22+
{
23+
}
24+
25+
/// <inheritdoc/>
26+
public TViewModel? ViewModel
27+
{
28+
get => _viewModel;
29+
set => this.RaiseAndSetIfChanged(ref _viewModel, value);
30+
}
31+
32+
/// <inheritdoc/>
33+
object? IViewFor.ViewModel
34+
{
35+
get => _viewModel;
36+
set => _viewModel = (TViewModel?)value;
37+
}
38+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// Copyright (c) 2024 .NET Foundation and Contributors. All rights reserved.
2+
// Licensed to the .NET Foundation under one or more agreements.
3+
// The .NET Foundation licenses this file to you under the MIT license.
4+
// See the LICENSE file in the project root for full license information.
5+
6+
namespace ReactiveUI.AndroidX;
7+
8+
/// <summary>
9+
/// This is a Fragment that is both an Activity and has ReactiveObject powers
10+
/// (i.e. you can call RaiseAndSetIfChanged).
11+
/// </summary>
12+
public class ReactiveDialogFragment : global::AndroidX.Fragment.App.DialogFragment, IReactiveNotifyPropertyChanged<ReactiveDialogFragment>, IReactiveObject, IHandleObservableErrors
13+
{
14+
private readonly Subject<Unit> _activated = new();
15+
private readonly Subject<Unit> _deactivated = new();
16+
17+
/// <summary>
18+
/// Initializes a new instance of the <see cref="ReactiveDialogFragment"/> class.
19+
/// </summary>
20+
protected ReactiveDialogFragment()
21+
{
22+
}
23+
24+
/// <inheritdoc/>
25+
public event PropertyChangingEventHandler? PropertyChanging;
26+
27+
/// <inheritdoc/>
28+
public event PropertyChangedEventHandler? PropertyChanged;
29+
30+
/// <inheritdoc/>
31+
public IObservable<Exception> ThrownExceptions => this.GetThrownExceptionsObservable();
32+
33+
/// <summary>
34+
/// Gets a observable that signals when the fragment is activated.
35+
/// </summary>
36+
public IObservable<Unit> Activated => _activated.AsObservable();
37+
38+
/// <summary>
39+
/// Gets a observable that signals when the fragment is deactivated.
40+
/// </summary>
41+
public IObservable<Unit> Deactivated => _deactivated.AsObservable();
42+
43+
/// <inheritdoc />
44+
public IObservable<IReactivePropertyChangedEventArgs<ReactiveDialogFragment>> Changing => this.GetChangingObservable();
45+
46+
/// <inheritdoc/>
47+
public IObservable<IReactivePropertyChangedEventArgs<ReactiveDialogFragment>> Changed => this.GetChangedObservable();
48+
49+
/// <inheritdoc/>
50+
void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
51+
52+
/// <inheritdoc/>
53+
void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
54+
55+
/// <inheritdoc />
56+
public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
57+
58+
/// <inheritdoc/>
59+
public override void OnPause()
60+
{
61+
base.OnPause();
62+
_deactivated.OnNext(Unit.Default);
63+
}
64+
65+
/// <inheritdoc/>
66+
public override void OnResume()
67+
{
68+
base.OnResume();
69+
_activated.OnNext(Unit.Default);
70+
}
71+
72+
/// <inheritdoc/>
73+
protected override void Dispose(bool disposing)
74+
{
75+
if (disposing)
76+
{
77+
_activated.Dispose();
78+
_deactivated.Dispose();
79+
}
80+
81+
base.Dispose(disposing);
82+
}
83+
}

0 commit comments

Comments
 (0)