diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props
index f2c26f710d..3b49e16fc9 100644
--- a/src/Directory.Packages.props
+++ b/src/Directory.Packages.props
@@ -38,6 +38,11 @@
+
+
+
+
+
diff --git a/src/ReactiveUI.AndroidX/ControlFetcherMixin.cs b/src/ReactiveUI.AndroidX/ControlFetcherMixin.cs
new file mode 100644
index 0000000000..e2522510f9
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ControlFetcherMixin.cs
@@ -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;
+
+///
+/// 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.
+///
+public static class ControlFetcherMixin
+{
+ ///
+ /// Wires a control to a property.
+ /// This should be called in the Fragment's OnCreateView, with the newly inflated layout.
+ ///
+ /// The fragment.
+ /// The inflated view.
+ /// The resolve members.
+ 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);
+ }
+ }
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/GlobalUsings.cs b/src/ReactiveUI.AndroidX/GlobalUsings.cs
new file mode 100644
index 0000000000..954a4fb5ce
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/GlobalUsings.cs
@@ -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;
diff --git a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs
new file mode 100644
index 0000000000..89934d0287
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity.cs
@@ -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;
+
+///
+/// This is an Activity that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+public class ReactiveAppCompatActivity : AppCompatActivity, IReactiveObject, IReactiveNotifyPropertyChanged, IHandleObservableErrors
+{
+ private readonly Subject _activated = new();
+ private readonly Subject _deactivated = new();
+ private readonly Subject<(int requestCode, Result result, Intent? intent)> _activityResult = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactiveAppCompatActivity()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The handle.
+ /// The ownership.
+ protected ReactiveAppCompatActivity(in IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ public IObservable> Changing => this.GetChangingObservable();
+
+ ///
+ public IObservable> Changed => this.GetChangedObservable();
+
+ ///
+ public IObservable ThrownExceptions => this.GetThrownExceptionsObservable();
+
+ ///
+ /// Gets a signal when activated.
+ ///
+ ///
+ /// The activated.
+ ///
+ public IObservable Activated => _activated.AsObservable();
+
+ ///
+ /// Gets a signal when deactivated.
+ ///
+ ///
+ /// The deactivated.
+ ///
+ public IObservable Deactivated => _deactivated.AsObservable();
+
+ ///
+ /// Gets the activity result.
+ ///
+ ///
+ /// The activity result.
+ ///
+ public IObservable<(int requestCode, Result result, Intent? intent)> ActivityResult => _activityResult.AsObservable();
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+ ///
+ public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
+
+ ///
+ /// Starts the activity for result asynchronously.
+ ///
+ /// The intent.
+ /// The request code.
+ /// A task with the result and intent.
+ 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;
+ }
+
+ ///
+ /// Starts the activity for result asynchronously.
+ ///
+ /// The type.
+ /// The request code.
+ /// A task with the result and intent.
+ 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;
+ }
+
+ ///
+ protected override void OnPause()
+ {
+ base.OnPause();
+ _deactivated.OnNext(Unit.Default);
+ }
+
+ ///
+ protected override void OnResume()
+ {
+ base.OnResume();
+ _activated.OnNext(Unit.Default);
+ }
+
+ ///
+ protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
+ {
+ base.OnActivityResult(requestCode, resultCode, data);
+ _activityResult.OnNext((requestCode, resultCode, data));
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _activated.Dispose();
+ _deactivated.Dispose();
+ _activityResult.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs
new file mode 100644
index 0000000000..de69ae77c2
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveAppCompatActivity{TViewModel}.cs
@@ -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;
+
+///
+/// This is an Activity that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+/// The view model type.
+public class ReactiveAppCompatActivity : ReactiveAppCompatActivity, IViewFor, ICanActivate
+ where TViewModel : class
+{
+ private TViewModel? _viewModel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactiveAppCompatActivity()
+ {
+ }
+
+ ///
+ public TViewModel? ViewModel
+ {
+ get => _viewModel;
+ set => this.RaiseAndSetIfChanged(ref _viewModel, value);
+ }
+
+ ///
+ object? IViewFor.ViewModel
+ {
+ get => _viewModel;
+ set => _viewModel = (TViewModel?)value;
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs b/src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs
new file mode 100644
index 0000000000..3fa86f7f55
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveDialogFragment.cs
@@ -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;
+
+///
+/// This is a Fragment that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+public class ReactiveDialogFragment : global::AndroidX.Fragment.App.DialogFragment, IReactiveNotifyPropertyChanged, IReactiveObject, IHandleObservableErrors
+{
+ private readonly Subject _activated = new();
+ private readonly Subject _deactivated = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactiveDialogFragment()
+ {
+ }
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ public IObservable ThrownExceptions => this.GetThrownExceptionsObservable();
+
+ ///
+ /// Gets a observable that signals when the fragment is activated.
+ ///
+ public IObservable Activated => _activated.AsObservable();
+
+ ///
+ /// Gets a observable that signals when the fragment is deactivated.
+ ///
+ public IObservable Deactivated => _deactivated.AsObservable();
+
+ ///
+ public IObservable> Changing => this.GetChangingObservable();
+
+ ///
+ public IObservable> Changed => this.GetChangedObservable();
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+ ///
+ public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
+
+ ///
+ public override void OnPause()
+ {
+ base.OnPause();
+ _deactivated.OnNext(Unit.Default);
+ }
+
+ ///
+ public override void OnResume()
+ {
+ base.OnResume();
+ _activated.OnNext(Unit.Default);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _activated.Dispose();
+ _deactivated.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveDialogFragment{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveDialogFragment{TViewModel}.cs
new file mode 100644
index 0000000000..97e12f2fe2
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveDialogFragment{TViewModel}.cs
@@ -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;
+
+///
+/// This is a DialogFragment that is both a DialogFragment and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+/// The view model type.
+public class ReactiveDialogFragment : ReactiveDialogFragment, IViewFor, ICanActivate
+ where TViewModel : class
+{
+ private TViewModel? _viewModel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactiveDialogFragment()
+ {
+ }
+
+ ///
+ public TViewModel? ViewModel
+ {
+ get => _viewModel;
+ set => this.RaiseAndSetIfChanged(ref _viewModel, value);
+ }
+
+ ///
+ object? IViewFor.ViewModel
+ {
+ get => _viewModel;
+ set => _viewModel = (TViewModel?)value;
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveFragment.cs b/src/ReactiveUI.AndroidX/ReactiveFragment.cs
new file mode 100644
index 0000000000..3c694e5e38
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveFragment.cs
@@ -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;
+
+///
+/// This is a Fragment that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+public class ReactiveFragment : global::AndroidX.Fragment.App.Fragment, IReactiveNotifyPropertyChanged, IReactiveObject, IHandleObservableErrors
+{
+ private readonly Subject _activated = new();
+ private readonly Subject _deactivated = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactiveFragment()
+ {
+ }
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ public IObservable ThrownExceptions => this.GetThrownExceptionsObservable();
+
+ ///
+ /// Gets a signal when the fragment is activated.
+ ///
+ public IObservable Activated => _activated.AsObservable();
+
+ ///
+ /// Gets a signal when the fragment is deactivated.
+ ///
+ public IObservable Deactivated => _deactivated.AsObservable();
+
+ ///
+ public IObservable> Changing => this.GetChangingObservable();
+
+ ///
+ public IObservable> Changed => this.GetChangedObservable();
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+ ///
+ public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
+
+ ///
+ public override void OnPause()
+ {
+ base.OnPause();
+ _deactivated.OnNext(Unit.Default);
+ }
+
+ ///
+ public override void OnResume()
+ {
+ base.OnResume();
+ _activated.OnNext(Unit.Default);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _activated.Dispose();
+ _deactivated.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs
new file mode 100644
index 0000000000..9cc7050fa0
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity.cs
@@ -0,0 +1,137 @@
+// 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 AndroidX.Fragment.App;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// This is an Activity that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+public class ReactiveFragmentActivity : FragmentActivity, IReactiveObject, IReactiveNotifyPropertyChanged, IHandleObservableErrors
+{
+ private readonly Subject _activated = new();
+ private readonly Subject _deactivated = new();
+ private readonly Subject<(int requestCode, Result result, Intent intent)> _activityResult = new();
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ public IObservable> Changing => this.GetChangingObservable();
+
+ ///
+ public IObservable> Changed => this.GetChangedObservable();
+
+ ///
+ public IObservable ThrownExceptions => this.GetThrownExceptionsObservable();
+
+ ///
+ /// Gets a signal when the activity fragment is activated.
+ ///
+ public IObservable Activated => _activated.AsObservable();
+
+ ///
+ /// Gets a signal when the activity fragment is deactivated.
+ ///
+ public IObservable Deactivated => _deactivated.AsObservable();
+
+ ///
+ /// Gets the activity result.
+ ///
+ public IObservable<(int requestCode, Result result, Intent intent)> ActivityResult => _activityResult.AsObservable();
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+ ///
+ public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
+
+ ///
+ /// Starts the activity for result asynchronously.
+ ///
+ /// The intent.
+ /// The request code.
+ /// A task with the result and intent.
+ 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;
+ }
+
+ ///
+ /// Starts the activity for result asynchronously.
+ ///
+ /// The type.
+ /// The request code.
+ /// A task with the result and intent.
+ 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;
+ }
+
+ ///
+ protected override void OnPause()
+ {
+ base.OnPause();
+ _deactivated.OnNext(Unit.Default);
+ }
+
+ ///
+ protected override void OnResume()
+ {
+ base.OnResume();
+ _activated.OnNext(Unit.Default);
+ }
+
+ ///
+ protected override void OnActivityResult(int requestCode, Result resultCode, Intent? data)
+ {
+ ArgumentNullException.ThrowIfNull(data);
+
+ base.OnActivityResult(requestCode, resultCode, data);
+ _activityResult.OnNext((requestCode, resultCode, data));
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _activated.Dispose();
+ _deactivated.Dispose();
+ _activityResult.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveFragmentActivity{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity{TViewModel}.cs
new file mode 100644
index 0000000000..b60743ae8d
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveFragmentActivity{TViewModel}.cs
@@ -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;
+
+///
+/// This is an Activity that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+/// The view model type.
+public class ReactiveFragmentActivity : ReactiveFragmentActivity, IViewFor, ICanActivate
+ where TViewModel : class
+{
+ private TViewModel? _viewModel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactiveFragmentActivity()
+ {
+ }
+
+ ///
+ public TViewModel? ViewModel
+ {
+ get => _viewModel;
+ set => this.RaiseAndSetIfChanged(ref _viewModel, value);
+ }
+
+ ///
+ object? IViewFor.ViewModel
+ {
+ get => _viewModel;
+ set => _viewModel = (TViewModel?)value;
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveFragment{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactiveFragment{TViewModel}.cs
new file mode 100644
index 0000000000..1faa368726
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveFragment{TViewModel}.cs
@@ -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;
+
+///
+/// This is a Fragment that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+/// The view model type.
+public class ReactiveFragment : ReactiveFragment, IViewFor, ICanActivate
+ where TViewModel : class
+{
+ private TViewModel? _viewModel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactiveFragment()
+ {
+ }
+
+ ///
+ public TViewModel? ViewModel
+ {
+ get => _viewModel;
+ set => this.RaiseAndSetIfChanged(ref _viewModel, value);
+ }
+
+ ///
+ object? IViewFor.ViewModel
+ {
+ get => _viewModel;
+ set => _viewModel = (TViewModel?)value;
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactivePagerAdapter.cs b/src/ReactiveUI.AndroidX/ReactivePagerAdapter.cs
new file mode 100644
index 0000000000..76fb1f8487
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactivePagerAdapter.cs
@@ -0,0 +1,100 @@
+// 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 AndroidX.ViewPager.Widget;
+
+using DynamicData;
+
+using Object = Java.Lang.Object;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// ReactivePagerAdapter is a PagerAdapter that will interface with a
+/// Observable change set, in a similar fashion to ReactiveTableViewSource.
+///
+/// The view model type.
+public class ReactivePagerAdapter : PagerAdapter, IEnableLogger
+ where TViewModel : class
+{
+ private readonly SourceList _list;
+ private readonly Func _viewCreator;
+ private readonly Action? _viewInitializer;
+ private readonly IDisposable _inner;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The change set to page.
+ /// A function which will create the view.
+ /// A action which will initialize a view.
+ public ReactivePagerAdapter(
+ IObservable> changeSet,
+ Func viewCreator,
+ Action? viewInitializer = null)
+ {
+ _list = new SourceList(changeSet);
+ _viewCreator = viewCreator;
+ _viewInitializer = viewInitializer;
+
+ _inner = _list.Connect().Subscribe(_ => NotifyDataSetChanged());
+ }
+
+ ///
+ public override int Count => _list.Count;
+
+ ///
+ public override bool IsViewFromObject(View view, Object @object) => (View)@object == view;
+
+ ///
+ public override Object InstantiateItem(ViewGroup container, int position)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+
+ var data = _list.Items[position];
+
+ // NB: PagerAdapter does not recycle itself.
+ var theView = _viewCreator(data, container);
+
+ if (theView.GetViewHost() is IViewFor ivf)
+ {
+ ivf.ViewModel = data;
+ }
+
+ _viewInitializer?.Invoke(data, theView);
+
+ container.AddView(theView, 0);
+ return theView;
+ }
+
+ ///
+ public override void DestroyItem(ViewGroup container, int position, Object @object)
+ {
+ ArgumentNullException.ThrowIfNull(container);
+
+ ArgumentNullException.ThrowIfNull(@object);
+
+ if (@object is not View view)
+ {
+ throw new ArgumentException("Item must be of type View", nameof(@object));
+ }
+
+ container.RemoveView(view);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _inner.Dispose();
+ _list.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactivePagerAdapter{TViewModel,TCollection}.cs b/src/ReactiveUI.AndroidX/ReactivePagerAdapter{TViewModel,TCollection}.cs
new file mode 100644
index 0000000000..5af87b1b36
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactivePagerAdapter{TViewModel,TCollection}.cs
@@ -0,0 +1,32 @@
+// 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 System.Collections.Specialized;
+
+using Android.Views;
+
+using DynamicData;
+using DynamicData.Binding;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// ReactivePagerAdapter is a PagerAdapter that will interface with a
+/// Observable change set, in a similar fashion to ReactiveTableViewSource.
+///
+/// The view model type.
+/// The type of collection.
+///
+/// Initializes a new instance of the class.
+///
+/// The collection to page.
+/// The function which will create the view.
+/// A action which will initialize the view.
+public class ReactivePagerAdapter(
+ TCollection collection,
+ Func viewCreator,
+ Action? viewInitializer = null) : ReactivePagerAdapter(collection.ToObservableChangeSet(), viewCreator, viewInitializer)
+ where TViewModel : class
+ where TCollection : INotifyCollectionChanged, IEnumerable;
diff --git a/src/ReactiveUI.AndroidX/ReactivePreferenceFragment.cs b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment.cs
new file mode 100644
index 0000000000..93ea575e4b
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment.cs
@@ -0,0 +1,96 @@
+// 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.Runtime;
+using AndroidX.Preference;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// This is a PreferenceFragment that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+public abstract class ReactivePreferenceFragment : PreferenceFragmentCompat, IReactiveNotifyPropertyChanged, IReactiveObject, IHandleObservableErrors
+{
+ private readonly Subject _activated = new();
+ private readonly Subject _deactivated = new();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactivePreferenceFragment()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The handle.
+ /// The ownership.
+ protected ReactivePreferenceFragment(in IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ public IObservable> Changing => this.GetChangingObservable();
+
+ ///
+ public IObservable> Changed => this.GetChangedObservable();
+
+ ///
+ public IObservable ThrownExceptions => this.GetThrownExceptionsObservable();
+
+ ///
+ /// Gets a signal when the fragment is activated.
+ ///
+ public IObservable Activated => _activated.AsObservable();
+
+ ///
+ /// Gets a signal when the fragment is deactivated.
+ ///
+ public IObservable Deactivated => _deactivated.AsObservable();
+
+ ///
+ public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
+
+ ///
+ public override void OnPause()
+ {
+ base.OnPause();
+ _deactivated.OnNext(Unit.Default);
+ }
+
+ ///
+ public override void OnResume()
+ {
+ base.OnResume();
+ _activated.OnNext(Unit.Default);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _activated.Dispose();
+ _deactivated.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactivePreferenceFragment{TViewModel}.cs b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment{TViewModel}.cs
new file mode 100644
index 0000000000..bb31a71e87
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactivePreferenceFragment{TViewModel}.cs
@@ -0,0 +1,50 @@
+// 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.Runtime;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// This is a PreferenceFragment that is both an Activity and has ReactiveObject powers
+/// (i.e. you can call RaiseAndSetIfChanged).
+///
+/// The view model type.
+public abstract class ReactivePreferenceFragment : ReactivePreferenceFragment, IViewFor, ICanActivate
+ where TViewModel : class
+{
+ private TViewModel? _viewModel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ protected ReactivePreferenceFragment()
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The handle.
+ /// The ownership.
+ protected ReactivePreferenceFragment(in IntPtr handle, JniHandleOwnership ownership)
+ : base(handle, ownership)
+ {
+ }
+
+ ///
+ public TViewModel? ViewModel
+ {
+ get => _viewModel;
+ set => this.RaiseAndSetIfChanged(ref _viewModel, value);
+ }
+
+ ///
+ object? IViewFor.ViewModel
+ {
+ get => _viewModel;
+ set => _viewModel = (TViewModel?)value;
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveRecyclerViewAdapter.cs b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewAdapter.cs
new file mode 100644
index 0000000000..3e2d212f65
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewAdapter.cs
@@ -0,0 +1,104 @@
+// 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 AndroidX.RecyclerView.Widget;
+using DynamicData;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// An adapter for the Android .
+///
+/// The type of ViewModel that this adapter holds.
+public abstract class ReactiveRecyclerViewAdapter : RecyclerView.Adapter
+ where TViewModel : class, IReactiveObject
+{
+ private readonly SourceList _list;
+
+ private readonly IDisposable _inner;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The backing list.
+ protected ReactiveRecyclerViewAdapter(IObservable> backingList)
+ {
+ _list = new SourceList(backingList);
+
+ _inner = _list
+ .Connect()
+ .ForEachChange(UpdateBindings)
+ .Subscribe();
+ }
+
+ ///
+ public override int ItemCount => _list.Count;
+
+ ///
+ public override int GetItemViewType(int position) => GetItemViewType(position, GetViewModelByPosition(position));
+
+ ///
+ /// Determine the View that will be used/re-used in lists where
+ /// the list contains different cell designs.
+ ///
+ /// The position of the current view in the list.
+ /// The ViewModel associated with the current View.
+ /// An ID to be used in OnCreateViewHolder.
+ public virtual int GetItemViewType(int position, TViewModel? viewModel) => base.GetItemViewType(position);
+
+ ///
+ public override void OnBindViewHolder(RecyclerView.ViewHolder holder, int position)
+ {
+ ArgumentNullException.ThrowIfNull(holder);
+
+ if (holder is not IViewFor viewForHolder)
+ {
+ throw new ArgumentException("Holder must be derived from IViewFor", nameof(holder));
+ }
+
+ viewForHolder.ViewModel = GetViewModelByPosition(position);
+ }
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ _inner.Dispose();
+ _list.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ private TViewModel? GetViewModelByPosition(int position) => position >= _list.Count ? null : _list.Items[position];
+
+ private void UpdateBindings(Change change)
+ {
+ switch (change.Reason)
+ {
+ case ListChangeReason.Add:
+ NotifyItemInserted(change.Item.CurrentIndex);
+ break;
+ case ListChangeReason.Remove:
+ NotifyItemRemoved(change.Item.CurrentIndex);
+ break;
+ case ListChangeReason.Moved:
+ NotifyItemMoved(change.Item.PreviousIndex, change.Item.CurrentIndex);
+ break;
+ case ListChangeReason.Replace:
+ case ListChangeReason.Refresh:
+ NotifyItemChanged(change.Item.CurrentIndex);
+ break;
+ case ListChangeReason.AddRange:
+ NotifyItemRangeInserted(change.Range.Index, change.Range.Count);
+ break;
+ case ListChangeReason.RemoveRange:
+ case ListChangeReason.Clear:
+ NotifyItemRangeRemoved(change.Range.Index, change.Range.Count);
+ break;
+ }
+ }
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveRecyclerViewAdapter{TViewModel,TCollection}.cs b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewAdapter{TViewModel,TCollection}.cs
new file mode 100644
index 0000000000..1059bde3db
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewAdapter{TViewModel,TCollection}.cs
@@ -0,0 +1,26 @@
+// 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 System.Collections.Specialized;
+
+using AndroidX.RecyclerView.Widget;
+
+using DynamicData;
+using DynamicData.Binding;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// An adapter for the Android .
+///
+/// The type of ViewModel that this adapter holds.
+/// The type of collection.
+///
+/// Initializes a new instance of the class.
+///
+/// The backing list.
+public abstract class ReactiveRecyclerViewAdapter(TCollection backingList) : ReactiveRecyclerViewAdapter(backingList.ToObservableChangeSet())
+ where TViewModel : class, IReactiveObject
+ where TCollection : ICollection, INotifyCollectionChanged;
diff --git a/src/ReactiveUI.AndroidX/ReactiveRecyclerViewViewHolder.cs b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewViewHolder.cs
new file mode 100644
index 0000000000..89aa191cc6
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveRecyclerViewViewHolder.cs
@@ -0,0 +1,204 @@
+// 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 System.Reflection;
+using System.Runtime.Serialization;
+using System.Text.Json.Serialization;
+using Android.Views;
+using AndroidX.RecyclerView.Widget;
+
+namespace ReactiveUI.AndroidX;
+
+///
+/// A implementation that binds to a reactive view model.
+///
+/// The type of the view model.
+public class ReactiveRecyclerViewViewHolder : RecyclerView.ViewHolder, ILayoutViewHost, IViewFor, IReactiveNotifyPropertyChanged>, IReactiveObject, ICanActivate
+ where TViewModel : class, IReactiveObject
+{
+ ///
+ /// Gets all public accessible properties.
+ ///
+ [SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401: Field should be private", Justification = "Legacy reasons")]
+ [SuppressMessage("Design", "CA1051: Do not declare visible instance fields", Justification = "Legacy reasons")]
+ [IgnoreDataMember]
+ [JsonIgnore]
+ protected Lazy? AllPublicProperties;
+
+ private readonly Subject _activated = new();
+
+ private readonly Subject _deactivated = new();
+
+ private TViewModel? _viewModel;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The view.
+ protected ReactiveRecyclerViewViewHolder(View view)
+ : base(view)
+ {
+ SetupRxObj();
+ ArgumentNullException.ThrowIfNull(view);
+
+ view.ViewAttachedToWindow += OnViewAttachedToWindow;
+ view.ViewDetachedFromWindow += OnViewDetachedFromWindow;
+
+ Selected = Observable.FromEvent(
+ eventHandler =>
+ {
+ void Handler(object? sender, EventArgs e) => eventHandler(AbsoluteAdapterPosition);
+ return Handler;
+ },
+ h => view.Click += h,
+ h => view.Click -= h);
+
+ LongClicked = Observable.FromEvent, int>(
+ eventHandler =>
+ {
+ void Handler(object? sender, View.LongClickEventArgs e) => eventHandler(AbsoluteAdapterPosition);
+
+ return Handler;
+ },
+ h => view.LongClick += h,
+ h => view.LongClick -= h);
+
+ SelectedWithViewModel = Observable.FromEvent(
+ eventHandler =>
+ {
+ void Handler(object? sender, EventArgs e) => eventHandler(ViewModel);
+ return Handler;
+ },
+ h => view.Click += h,
+ h => view.Click -= h);
+
+ LongClickedWithViewModel = Observable.FromEvent, TViewModel?>(
+ eventHandler =>
+ {
+ void Handler(object? sender, View.LongClickEventArgs e) => eventHandler(ViewModel);
+ return Handler;
+ },
+ h => view.LongClick += h,
+ h => view.LongClick -= h);
+ }
+
+ ///
+ public event PropertyChangingEventHandler? PropertyChanging;
+
+ ///
+ public event PropertyChangedEventHandler? PropertyChanged;
+
+ ///
+ /// Gets an observable that signals that this ViewHolder has been selected.
+ ///
+ /// The is the position of this ViewHolder in the
+ /// and corresponds to the property.
+ ///
+ ///
+ public IObservable Selected { get; }
+
+ ///
+ /// Gets an observable that signals that this ViewHolder has been selected.
+ /// The is the ViewModel of this ViewHolder in the .
+ ///
+ public IObservable SelectedWithViewModel { get; }
+
+ ///
+ /// Gets an observable that signals that this ViewHolder has been long-clicked.
+ ///
+ /// The is the position of this ViewHolder in the
+ /// and corresponds to the property.
+ ///
+ ///
+ public IObservable LongClicked { get; }
+
+ ///
+ /// Gets an observable that signals that this ViewHolder has been long-clicked.
+ /// The is the ViewModel of this ViewHolder in the .
+ ///
+ public IObservable LongClickedWithViewModel { get; }
+
+ ///
+ public IObservable Activated => _activated.AsObservable();
+
+ ///
+ public IObservable Deactivated => _deactivated.AsObservable();
+
+ ///
+ /// Gets the current view being shown.
+ ///
+ public View View => ItemView;
+
+ ///
+ public TViewModel? ViewModel
+ {
+ get => _viewModel;
+ set => this.RaiseAndSetIfChanged(ref _viewModel, value);
+ }
+
+ ///
+ /// Gets an observable which signals when exceptions are thrown.
+ ///
+ [IgnoreDataMember]
+ [JsonIgnore]
+ public IObservable ThrownExceptions => this.GetThrownExceptionsObservable();
+
+ ///
+ object? IViewFor.ViewModel
+ {
+ get => ViewModel;
+ set => ViewModel = (TViewModel?)value;
+ }
+
+ ///
+ [IgnoreDataMember]
+ [JsonIgnore]
+ public IObservable>> Changing => this.GetChangingObservable();
+
+ ///
+ [IgnoreDataMember]
+ [JsonIgnore]
+ public IObservable>> Changed => this.GetChangedObservable();
+
+ ///
+ public IDisposable SuppressChangeNotifications() => IReactiveObjectExtensions.SuppressChangeNotifications(this);
+
+ ///
+ /// Gets if change notifications via the INotifyPropertyChanged interface are being sent.
+ ///
+ /// A value indicating whether change notifications are enabled or not.
+ public bool AreChangeNotificationsEnabled() => IReactiveObjectExtensions.AreChangeNotificationsEnabled(this);
+
+ ///
+ void IReactiveObject.RaisePropertyChanging(PropertyChangingEventArgs args) => PropertyChanging?.Invoke(this, args);
+
+ ///
+ void IReactiveObject.RaisePropertyChanged(PropertyChangedEventArgs args) => PropertyChanged?.Invoke(this, args);
+
+ ///
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ View.ViewAttachedToWindow -= OnViewAttachedToWindow;
+ View.ViewDetachedFromWindow -= OnViewDetachedFromWindow;
+
+ _activated.Dispose();
+ _deactivated.Dispose();
+ }
+
+ base.Dispose(disposing);
+ }
+
+ [OnDeserialized]
+ private void SetupRxObj(in StreamingContext sc) => SetupRxObj();
+
+ private void SetupRxObj() =>
+ AllPublicProperties = new Lazy(() => [.. GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance)]);
+
+ private void OnViewAttachedToWindow(object? sender, View.ViewAttachedToWindowEventArgs args) => _activated.OnNext(Unit.Default);
+
+ private void OnViewDetachedFromWindow(object? sender, View.ViewDetachedFromWindowEventArgs args) => _deactivated.OnNext(Unit.Default);
+}
diff --git a/src/ReactiveUI.AndroidX/ReactiveUI.AndroidX.csproj b/src/ReactiveUI.AndroidX/ReactiveUI.AndroidX.csproj
new file mode 100644
index 0000000000..553336a9d8
--- /dev/null
+++ b/src/ReactiveUI.AndroidX/ReactiveUI.AndroidX.csproj
@@ -0,0 +1,19 @@
+
+
+ net8.0-android
+ Provides ReactiveUI extensions for the AndroidX Library
+ ReactiveUI.AndroidX
+ mvvm;reactiveui;rx;reactive extensions;observable;LINQ;events;frp;xamarin;androidx;forms;xamarin.androidx;net;
+ 34.0
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet6_0.verified.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet6_0.verified.txt
index ca4c6ffa6f..a9f915f06b 100644
--- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet6_0.verified.txt
+++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet6_0.verified.txt
@@ -1,4 +1,5 @@
-[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Blazor")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.AndroidX")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Blazor")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Drawing")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Tests")]
diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt
index 1910660e09..bb602deebe 100644
--- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt
+++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt
@@ -1,4 +1,5 @@
-[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Blazor")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.AndroidX")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Blazor")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Drawing")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Tests")]
diff --git a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.Net4_7.verified.txt b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.Net4_7.verified.txt
index a94975353f..781f7821ce 100644
--- a/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.Net4_7.verified.txt
+++ b/src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.Net4_7.verified.txt
@@ -1,4 +1,5 @@
-[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Blazor")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.AndroidX")]
+[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Blazor")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Drawing")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Maui")]
[assembly: System.Runtime.CompilerServices.InternalsVisibleTo("ReactiveUI.Tests")]
diff --git a/src/ReactiveUI.sln b/src/ReactiveUI.sln
index b6484b67a4..e60b06e689 100644
--- a/src/ReactiveUI.sln
+++ b/src/ReactiveUI.sln
@@ -1,6 +1,6 @@
Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 17
+# 17
VisualStudioVersion = 17.0.31825.309
MinimumVisualStudioVersion = 16.0.31613.86
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{BD9762CF-E104-481C-96A6-26E624B86283}"
@@ -47,6 +47,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.Maui", "Reactive
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.WinUI", "ReactiveUI.WinUI\ReactiveUI.WinUI.csproj", "{4FF9F04B-928E-47B6-836F-546B584F597C}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ReactiveUI.AndroidX", "ReactiveUI.AndroidX\ReactiveUI.AndroidX.csproj", "{3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -261,6 +263,22 @@ Global
{4FF9F04B-928E-47B6-836F-546B584F597C}.Release|x64.Build.0 = Release|Any CPU
{4FF9F04B-928E-47B6-836F-546B584F597C}.Release|x86.ActiveCfg = Release|Any CPU
{4FF9F04B-928E-47B6-836F-546B584F597C}.Release|x86.Build.0 = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|arm64.ActiveCfg = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|arm64.Build.0 = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x64.Build.0 = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Debug|x86.Build.0 = Debug|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|arm64.ActiveCfg = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|arm64.Build.0 = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x64.ActiveCfg = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x64.Build.0 = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x86.ActiveCfg = Release|Any CPU
+ {3F642A3D-CBA6-4E17-903C-672FDBE9D9E8}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/src/ReactiveUI/Properties/AssemblyInfo.cs b/src/ReactiveUI/Properties/AssemblyInfo.cs
index 21cb45aab7..ef738da8ff 100644
--- a/src/ReactiveUI/Properties/AssemblyInfo.cs
+++ b/src/ReactiveUI/Properties/AssemblyInfo.cs
@@ -14,3 +14,4 @@
[assembly: InternalsVisibleTo("ReactiveUI.Uno.WinUI")]
[assembly: InternalsVisibleTo("ReactiveUI.Drawing")]
[assembly: InternalsVisibleTo("ReactiveUI.WinUI")]
+[assembly: InternalsVisibleTo("ReactiveUI.AndroidX")]