├── .gitignore ├── Droid ├── Assets │ └── AboutAssets.txt ├── MainActivity.cs ├── Properties │ ├── AndroidManifest.xml │ └── AssemblyInfo.cs ├── Resources │ ├── AboutResources.txt │ ├── Resource.designer.cs │ ├── drawable-hdpi │ │ └── icon.png │ ├── drawable-xhdpi │ │ └── icon.png │ ├── drawable-xxhdpi │ │ └── icon.png │ ├── drawable │ │ ├── cities.png │ │ ├── icon.png │ │ └── users.png │ ├── layout │ │ ├── Tabbar.axml │ │ └── Toolbar.axml │ └── values │ │ └── styles.xml ├── XamarinReduxDemo.Droid.csproj └── packages.config ├── LICENSE ├── README.md ├── XamarinReduxDemo.Core ├── App.cs ├── Modules │ ├── Cities │ │ └── List │ │ │ ├── CityListPage.cs │ │ │ └── CityListViewModel.cs │ └── Users │ │ ├── Form │ │ ├── UserFormPage.cs │ │ └── UserFormViewModel.cs │ │ ├── List │ │ ├── UserListPage.cs │ │ └── UserListViewModel.cs │ │ └── Profile │ │ ├── UserProfilePage.cs │ │ └── UserProfileViewModel.cs ├── Resources │ ├── 1.jpg │ ├── 2.jpg │ └── 3.jpg ├── Shared │ ├── SmartCollection.cs │ └── ViewModelBase.cs └── XamarinReduxDemo.Core.csproj ├── XamarinReduxDemo.Store ├── Actions.fs ├── Domain.fs ├── Reducers.fs ├── State.fs ├── Store.fs └── XamarinReduxDemo.Store.fsproj ├── XamarinReduxDemo.sln ├── demo.gif └── iOS ├── AppDelegate.cs ├── Assets.xcassets ├── AppIcon.appiconset │ └── Contents.json └── Contents.json ├── Entitlements.plist ├── Info.plist ├── LaunchScreen.storyboard ├── Main.cs ├── Resources ├── cities@2x.png └── users@2x.png ├── XamarinReduxDemo.iOS.csproj └── packages.config /.gitignore: -------------------------------------------------------------------------------- 1 | # Autosave files 2 | *~ 3 | 4 | # build 5 | [Oo]bj/ 6 | [Bb]in/ 7 | packages/ 8 | TestResults/ 9 | 10 | # globs 11 | Makefile.in 12 | *.DS_Store 13 | *.sln.cache 14 | *.suo 15 | *.cache 16 | *.pidb 17 | *.userprefs 18 | *.usertasks 19 | config.log 20 | config.make 21 | config.status 22 | aclocal.m4 23 | install-sh 24 | autom4te.cache/ 25 | *.user 26 | *.tar.gz 27 | tarballs/ 28 | test-results/ 29 | Thumbs.db 30 | 31 | # Mac bundle stuff 32 | *.dmg 33 | *.app 34 | 35 | # resharper 36 | *_Resharper.* 37 | *.Resharper 38 | 39 | # dotCover 40 | *.dotCover 41 | 42 | *.suo 43 | *.user 44 | *.userosscache 45 | *.sln.docstates 46 | */.idea/* 47 | */**/.idea 48 | -------------------------------------------------------------------------------- /Droid/Assets/AboutAssets.txt: -------------------------------------------------------------------------------- 1 | Any raw assets you want to be deployed with your application can be placed in 2 | this directory (and child directories) and given a Build Action of "AndroidAsset". 3 | 4 | These files will be deployed with your package and will be accessible using Android's 5 | AssetManager, like this: 6 | 7 | public class ReadAsset : Activity 8 | { 9 | protected override void OnCreate (Bundle bundle) 10 | { 11 | base.OnCreate (bundle); 12 | 13 | InputStream input = Assets.Open ("my_asset.txt"); 14 | } 15 | } 16 | 17 | Additionally, some Android functions will automatically load asset files: 18 | 19 | Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf"); 20 | -------------------------------------------------------------------------------- /Droid/MainActivity.cs: -------------------------------------------------------------------------------- 1 |  2 | using Android.App; 3 | using Android.Content.PM; 4 | using Android.OS; 5 | using XamarinReduxDemo.Core; 6 | 7 | namespace XamarinReduxDemo.Droid 8 | { 9 | [Activity(Label = "XamarinReduxDemo.Droid", Icon = "@drawable/icon", Theme = "@style/MyTheme", MainLauncher = true, ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation)] 10 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsAppCompatActivity 11 | { 12 | protected override void OnCreate(Bundle bundle) 13 | { 14 | TabLayoutResource = Resource.Layout.Tabbar; 15 | ToolbarResource = Resource.Layout.Toolbar; 16 | 17 | base.OnCreate(bundle); 18 | 19 | Xamarin.Forms.Forms.Init(this, bundle); 20 | 21 | LoadApplication(new App()); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Droid/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /Droid/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using Android.App; 4 | 5 | // Information about this assembly is defined by the following attributes. 6 | // Change them to the values specific to your project. 7 | 8 | [assembly: AssemblyTitle("XamarinReduxDemo.Droid")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("")] 13 | [assembly: AssemblyCopyright("${AuthorCopyright}")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}". 18 | // The form "{Major}.{Minor}.*" will automatically update the build and revision, 19 | // and "{Major}.{Minor}.{Build}.*" will update just the revision. 20 | 21 | [assembly: AssemblyVersion("1.0.0")] 22 | 23 | // The following attributes are used to specify the signing key for the assembly, 24 | // if desired. See the Mono documentation for more information about signing. 25 | 26 | //[assembly: AssemblyDelaySign(false)] 27 | //[assembly: AssemblyKeyFile("")] 28 | -------------------------------------------------------------------------------- /Droid/Resources/AboutResources.txt: -------------------------------------------------------------------------------- 1 | Images, layout descriptions, binary blobs and string dictionaries can be included 2 | in your application as resource files. Various Android APIs are designed to 3 | operate on the resource IDs instead of dealing with images, strings or binary blobs 4 | directly. 5 | 6 | For example, a sample Android app that contains a user interface layout (main.axml), 7 | an internationalization string table (strings.xml) and some icons (drawable-XXX/icon.png) 8 | would keep its resources in the "Resources" directory of the application: 9 | 10 | Resources/ 11 | drawable/ 12 | icon.png 13 | 14 | layout/ 15 | main.axml 16 | 17 | values/ 18 | strings.xml 19 | 20 | In order to get the build system to recognize Android resources, set the build action to 21 | "AndroidResource". The native Android APIs do not operate directly with filenames, but 22 | instead operate on resource IDs. When you compile an Android application that uses resources, 23 | the build system will package the resources for distribution and generate a class called "R" 24 | (this is an Android convention) that contains the tokens for each one of the resources 25 | included. For example, for the above Resources layout, this is what the R class would expose: 26 | 27 | public class R { 28 | public class drawable { 29 | public const int icon = 0x123; 30 | } 31 | 32 | public class layout { 33 | public const int main = 0x456; 34 | } 35 | 36 | public class strings { 37 | public const int first_string = 0xabc; 38 | public const int second_string = 0xbcd; 39 | } 40 | } 41 | 42 | You would then use R.drawable.icon to reference the drawable/icon.png file, or R.layout.main 43 | to reference the layout/main.axml file, or R.strings.first_string to reference the first 44 | string in the dictionary file values/strings.xml. 45 | -------------------------------------------------------------------------------- /Droid/Resources/drawable-hdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/Droid/Resources/drawable-hdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/Droid/Resources/drawable-xhdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xxhdpi/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/Droid/Resources/drawable-xxhdpi/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/cities.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/Droid/Resources/drawable/cities.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/Droid/Resources/drawable/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/users.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/Droid/Resources/drawable/users.png -------------------------------------------------------------------------------- /Droid/Resources/layout/Tabbar.axml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Droid/Resources/layout/Toolbar.axml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /Droid/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 5 | 6 | 24 | 27 | 28 | -------------------------------------------------------------------------------- /Droid/XamarinReduxDemo.Droid.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A} 8 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 9 | Library 10 | XamarinReduxDemo.Droid 11 | XamarinReduxDemo.Droid 12 | v7.1 13 | True 14 | Resources\Resource.designer.cs 15 | Resource 16 | Properties\AndroidManifest.xml 17 | Resources 18 | Assets 19 | false 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug 26 | DEBUG; 27 | prompt 28 | 4 29 | None 30 | 31 | 32 | true 33 | pdbonly 34 | true 35 | bin\Release 36 | prompt 37 | 4 38 | true 39 | false 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | ..\packages\Xamarin.Android.Support.Annotations.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Annotations.dll 48 | 49 | 50 | ..\packages\Xamarin.Android.Support.Compat.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Compat.dll 51 | 52 | 53 | ..\packages\Xamarin.Android.Support.Core.UI.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Core.UI.dll 54 | 55 | 56 | ..\packages\Xamarin.Android.Support.Core.Utils.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Core.Utils.dll 57 | 58 | 59 | ..\packages\Xamarin.Android.Support.Media.Compat.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Media.Compat.dll 60 | 61 | 62 | ..\packages\Xamarin.Android.Support.Fragment.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Fragment.dll 63 | 64 | 65 | ..\packages\Xamarin.Android.Support.v4.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v4.dll 66 | 67 | 68 | ..\packages\Xamarin.Android.Support.Vector.Drawable.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Vector.Drawable.dll 69 | 70 | 71 | ..\packages\Xamarin.Android.Support.Animated.Vector.Drawable.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Animated.Vector.Drawable.dll 72 | 73 | 74 | ..\packages\Xamarin.Android.Support.v7.AppCompat.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.AppCompat.dll 75 | 76 | 77 | ..\packages\Xamarin.Android.Support.Transition.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Transition.dll 78 | 79 | 80 | ..\packages\Xamarin.Android.Support.v7.RecyclerView.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.RecyclerView.dll 81 | 82 | 83 | ..\packages\Xamarin.Android.Support.Design.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.Design.dll 84 | 85 | 86 | ..\packages\Xamarin.Android.Support.v7.CardView.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.CardView.dll 87 | 88 | 89 | ..\packages\Xamarin.Android.Support.v7.Palette.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.Palette.dll 90 | 91 | 92 | ..\packages\Xamarin.Android.Support.v7.MediaRouter.25.4.0.2\lib\MonoAndroid70\Xamarin.Android.Support.v7.MediaRouter.dll 93 | 94 | 95 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\MonoAndroid10\FormsViewGroup.dll 96 | 97 | 98 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\MonoAndroid10\Xamarin.Forms.Core.dll 99 | 100 | 101 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\MonoAndroid10\Xamarin.Forms.Platform.Android.dll 102 | 103 | 104 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\MonoAndroid10\Xamarin.Forms.Platform.dll 105 | 106 | 107 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\MonoAndroid10\Xamarin.Forms.Xaml.dll 108 | 109 | 110 | ..\packages\MvvmNano.Core.3.2.0\lib\netstandard2.0\MvvmNano.Core.dll 111 | 112 | 113 | ..\packages\MvvmNano.Forms.3.2.0\lib\netstandard2.0\MvvmNano.Forms.dll 114 | 115 | 116 | ..\packages\MvvmNano.TinyIoC.3.2.0\lib\netstandard2.0\MvvmNano.TinyIoC.dll 117 | 118 | 119 | ..\packages\FSharp.Core.4.2.3\lib\netstandard1.6\FSharp.Core.dll 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A} 145 | XamarinReduxDemo.Store 146 | 147 | 148 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF} 149 | XamarinReduxDemo.Core 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /Droid/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Bandt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Building An F# Redux Store For C# Xamarin Apps 2 | 3 | ![Demo of Redux in Action within a Xamarin App](https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/master/demo.gif) 4 | 5 | This tiny demo project demonstrates how to implement a basic Redux Store for a C# Xamarin App with the help of F#. For more information about the motivation behind see the [blog post](https://thomasbandt.com/fsharp-redux-store-for-xamarin-apps). 6 | 7 | The app is using Xamarin.Forms and supports iOS and Android as target platforms. 8 | 9 | The MVVM Framework in use is [MvvmNano](https://github.com/aspnetde/MvvmNano). Icons are coming from [Icons8](https://icons8.com/). And all the nice people shown as users are provided by [Unsplash](https://unsplash.com/). Sorry for the ugly app layout, though ;-). 10 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/App.cs: -------------------------------------------------------------------------------- 1 | using MvvmNano; 2 | using MvvmNano.Forms; 3 | using MvvmNano.TinyIoC; 4 | using Xamarin.Forms; 5 | using XamarinReduxDemo.Core.Modules.Cities; 6 | using XamarinReduxDemo.Core.Modules.Users; 7 | using XamarinReduxDemo.Store; 8 | 9 | namespace XamarinReduxDemo.Core 10 | { 11 | public class App : MvvmNanoApplication 12 | { 13 | protected override void OnStart() 14 | { 15 | base.OnStart(); 16 | 17 | SetUpReduxStore(); 18 | SetUpTabbedMainPage(); 19 | } 20 | 21 | protected override IMvvmNanoIoCAdapter GetIoCAdapter() 22 | => new MvvmNanoTinyIoCAdapter(); 23 | 24 | private void SetUpReduxStore() 25 | { 26 | IStore store = CreateStore(); 27 | 28 | MvvmNanoIoC.RegisterAsSingleton(store); 29 | } 30 | 31 | private static IStore CreateStore() 32 | { 33 | IStore store = new Store.Store( 34 | // Loads up the instance of the Store with the last known State. 35 | // In this example a new state is always being created, but that's 36 | // the place where you should load your current State from disk 37 | // or whatever you prefer for persisting data. 38 | InitialState.Create(), 39 | 40 | // Register all Reducers that should be applied to dispatched 41 | // actions. 42 | RootReducer.Create() 43 | ); 44 | 45 | store.Subscribe(SubscriptionType.NewStateSubscription(state => 46 | { 47 | // The State was updated. That's the moment where you 48 | // should persist the current state. 49 | })); 50 | 51 | return store; 52 | } 53 | 54 | private void SetUpTabbedMainPage() 55 | { 56 | var tabbedPage = new TabbedPage(); 57 | 58 | tabbedPage.Children.Add(new MvvmNanoNavigationPage(GetPageFor()) 59 | { 60 | Title = "Users", 61 | Icon = "users" 62 | }); 63 | 64 | tabbedPage.Children.Add(new MvvmNanoNavigationPage(GetPageFor()) 65 | { 66 | Title = "Cities", 67 | Icon = "cities" 68 | }); 69 | 70 | MainPage = tabbedPage; 71 | } 72 | 73 | private static Page GetPageFor() where TViewModel : MvvmNanoViewModel 74 | { 75 | TViewModel viewModel = MvvmNanoIoC.Resolve(); 76 | viewModel.Initialize(); 77 | 78 | return ResolvePage(viewModel); 79 | } 80 | 81 | private static Page ResolvePage(TViewModel viewModel) where TViewModel : IViewModel 82 | { 83 | IView viewFor = MvvmNanoIoC.Resolve().CreateViewFor(); 84 | viewFor.SetViewModel(viewModel); 85 | return viewFor as Page; 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Cities/List/CityListPage.cs: -------------------------------------------------------------------------------- 1 | using MvvmNano.Forms; 2 | using Xamarin.Forms; 3 | using XamarinReduxDemo.Store; 4 | 5 | namespace XamarinReduxDemo.Core.Modules.Cities 6 | { 7 | public class CityListPage : MvvmNanoContentPage 8 | { 9 | private readonly ListView _listView; 10 | 11 | public CityListPage() 12 | { 13 | Title = "Cities"; 14 | 15 | Content = _listView = new ListView(); 16 | 17 | _listView.ItemSelected += OnItemSelected; 18 | 19 | _listView.ItemTemplate = new DataTemplate(typeof(TextCell)); 20 | _listView.ItemTemplate.SetBinding(TextCell.TextProperty, nameof(User.Name)); 21 | } 22 | 23 | public override void OnViewModelSet() 24 | { 25 | base.OnViewModelSet(); 26 | 27 | _listView.ItemsSource = ViewModel.Cities; 28 | } 29 | 30 | private async void OnItemSelected(object sender, SelectedItemChangedEventArgs arguments) 31 | { 32 | if (arguments.SelectedItem == null) 33 | { 34 | return; 35 | } 36 | 37 | const string cancel = "Cancel"; 38 | 39 | var action = await DisplayActionSheet( 40 | "Do you want to remove that city from the list?", 41 | cancel: cancel, 42 | destruction: "Yes, remove it" 43 | ); 44 | 45 | if (action != cancel) 46 | { 47 | ViewModel.RemoveCityCommand.Execute(arguments.SelectedItem); 48 | } 49 | 50 | _listView.SelectedItem = null; 51 | } 52 | 53 | public override void Dispose() 54 | { 55 | base.Dispose(); 56 | 57 | _listView.ItemSelected -= OnItemSelected; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Cities/List/CityListViewModel.cs: -------------------------------------------------------------------------------- 1 | using MvvmNano; 2 | using XamarinReduxDemo.Core.Shared; 3 | using XamarinReduxDemo.Store; 4 | 5 | namespace XamarinReduxDemo.Core.Modules.Cities 6 | { 7 | public class CityListViewModel : ViewModelBase 8 | { 9 | public SmartCollection Cities { get; } 10 | 11 | private MvvmNanoCommand _removeCityCommand; 12 | public MvvmNanoCommand RemoveCityCommand 13 | => _removeCityCommand ?? 14 | (_removeCityCommand = new MvvmNanoCommand(OnRemoveCity)); 15 | 16 | public CityListViewModel(IStore store) : base(store) 17 | { 18 | Cities = new SmartCollection(); 19 | 20 | ConnectToStore(state => state.Cities, cities => 21 | { 22 | Cities.Reset(cities); 23 | }); 24 | } 25 | 26 | private void OnRemoveCity(City city) 27 | { 28 | Store.Dispatch(StoreAction.NewCityRemoved(city)); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Users/Form/UserFormPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using MvvmNano.Forms; 3 | using Xamarin.Forms; 4 | 5 | namespace XamarinReduxDemo.Core.Modules.Users 6 | { 7 | public class UserFormPage : MvvmNanoContentPage 8 | { 9 | public UserFormPage() 10 | { 11 | Title = "Edit Profile"; 12 | } 13 | 14 | public override void OnViewModelSet() 15 | { 16 | base.OnViewModelSet(); 17 | 18 | ViewModel.Done += OnDone; 19 | 20 | ToolbarItems.Add(new ToolbarItem 21 | { 22 | Text = "Save", 23 | Command = ViewModel.SaveCommand 24 | }); 25 | 26 | var scrollView = new ScrollView 27 | { 28 | HorizontalOptions = LayoutOptions.FillAndExpand, 29 | VerticalOptions = LayoutOptions.Start, 30 | BackgroundColor = Color.White 31 | }; 32 | 33 | var layout = new StackLayout 34 | { 35 | HorizontalOptions = LayoutOptions.FillAndExpand, 36 | VerticalOptions = LayoutOptions.Start, 37 | Padding = 15, 38 | Spacing = 15 39 | }; 40 | 41 | var profileImage = new Image 42 | { 43 | WidthRequest = 300, 44 | HeightRequest = 300, 45 | HorizontalOptions = LayoutOptions.Center, 46 | VerticalOptions = LayoutOptions.Start, 47 | Source = ImageSource.FromResource(ViewModel.ImageName) 48 | }; 49 | 50 | var nameEntry = new Entry 51 | { 52 | FontSize = 20, 53 | WidthRequest = 300, 54 | HorizontalOptions = LayoutOptions.Center, 55 | VerticalOptions = LayoutOptions.Start, 56 | HorizontalTextAlignment = TextAlignment.Center, 57 | TextColor = Color.Black, 58 | Placeholder = "Name" 59 | }; 60 | BindToViewModel(nameEntry, Entry.TextProperty, x => x.Name); 61 | 62 | layout.Children.Add(profileImage); 63 | layout.Children.Add(nameEntry); 64 | scrollView.Content = layout; 65 | 66 | Content = new StackLayout 67 | { 68 | HorizontalOptions = LayoutOptions.FillAndExpand, 69 | VerticalOptions = LayoutOptions.Start, 70 | Padding = 0, 71 | Spacing = 0, 72 | Children = 73 | { 74 | scrollView 75 | } 76 | }; 77 | } 78 | 79 | private void OnDone(object sender, EventArgs eventArgs) 80 | { 81 | Navigation.PopAsync(true); 82 | } 83 | 84 | public override void Dispose() 85 | { 86 | base.Dispose(); 87 | 88 | ViewModel.Done -= OnDone; 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Users/Form/UserFormViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using MvvmNano; 4 | using XamarinReduxDemo.Core.Shared; 5 | using XamarinReduxDemo.Store; 6 | 7 | namespace XamarinReduxDemo.Core.Modules.Users 8 | { 9 | public class UserFormViewModel : ViewModelBase 10 | { 11 | private UserId _userId; 12 | 13 | private string _name; 14 | public string Name 15 | { 16 | get => _name; 17 | set { _name = value; NotifyPropertyChanged(); } 18 | } 19 | 20 | public string ImageName { get; private set; } 21 | 22 | private MvvmNanoCommand _saveCommand; 23 | public MvvmNanoCommand SaveCommand 24 | => _saveCommand ?? 25 | (_saveCommand = new MvvmNanoCommand(OnSave)); 26 | 27 | // Hack, because there's no Close mechanism in MvvmNano yet 28 | public event EventHandler Done; 29 | 30 | public UserFormViewModel(IStore store) : base(store) 31 | { 32 | } 33 | 34 | public override void Initialize(UserId userId) 35 | { 36 | _userId = userId; 37 | 38 | ConnectToStore(state => state.Users.SingleOrDefault(u => Equals(u.Id, _userId)), user => 39 | { 40 | if (user == null) 41 | { 42 | return; 43 | } 44 | 45 | Name = user.Name; 46 | ImageName = user.Id.Item + ".jpg"; 47 | }); 48 | } 49 | 50 | private void OnSave() 51 | { 52 | var updatedUser = new User( 53 | _userId, 54 | Name 55 | ); 56 | 57 | Store.Dispatch(StoreAction.NewUserUpdated(updatedUser)); 58 | 59 | Done?.Invoke(this, EventArgs.Empty); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Users/List/UserListPage.cs: -------------------------------------------------------------------------------- 1 | using MvvmNano.Forms; 2 | using Xamarin.Forms; 3 | using XamarinReduxDemo.Store; 4 | 5 | namespace XamarinReduxDemo.Core.Modules.Users 6 | { 7 | public class UserListPage : MvvmNanoContentPage 8 | { 9 | private readonly ListView _listView; 10 | 11 | public UserListPage() 12 | { 13 | Title = "Users"; 14 | 15 | Content = _listView = new ListView(); 16 | 17 | _listView.ItemSelected += OnItemSelected; 18 | 19 | _listView.ItemTemplate = new DataTemplate(typeof(TextCell)); 20 | _listView.ItemTemplate.SetBinding(TextCell.TextProperty, nameof(User.Name)); 21 | } 22 | 23 | public override void OnViewModelSet() 24 | { 25 | base.OnViewModelSet(); 26 | 27 | _listView.ItemsSource = ViewModel.Users; 28 | } 29 | 30 | private void OnItemSelected(object sender, SelectedItemChangedEventArgs arguments) 31 | { 32 | if (arguments.SelectedItem != null) 33 | { 34 | ViewModel.OpenUserProfileCommand.Execute(arguments.SelectedItem); 35 | } 36 | 37 | _listView.SelectedItem = null; 38 | } 39 | 40 | public override void Dispose() 41 | { 42 | base.Dispose(); 43 | 44 | _listView.ItemSelected -= OnItemSelected; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Users/List/UserListViewModel.cs: -------------------------------------------------------------------------------- 1 | using MvvmNano; 2 | using XamarinReduxDemo.Core.Shared; 3 | using XamarinReduxDemo.Store; 4 | 5 | namespace XamarinReduxDemo.Core.Modules.Users 6 | { 7 | public class UserListViewModel : ViewModelBase 8 | { 9 | public SmartCollection Users { get; } 10 | 11 | private MvvmNanoCommand _openUserProfileCommand; 12 | public MvvmNanoCommand OpenUserProfileCommand 13 | => _openUserProfileCommand ?? 14 | (_openUserProfileCommand = new MvvmNanoCommand(OnOpenUserProfile)); 15 | 16 | public UserListViewModel(IStore store) : base(store) 17 | { 18 | Users = new SmartCollection(); 19 | 20 | ConnectToStore(state => state.Users, users => 21 | { 22 | Users.Reset(users); 23 | }); 24 | } 25 | 26 | private void OnOpenUserProfile(User selectedUser) 27 | { 28 | NavigateTo(selectedUser.Id); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Users/Profile/UserProfilePage.cs: -------------------------------------------------------------------------------- 1 | using MvvmNano.Forms; 2 | using Xamarin.Forms; 3 | 4 | namespace XamarinReduxDemo.Core.Modules.Users 5 | { 6 | public class UserProfilePage : MvvmNanoContentPage 7 | { 8 | public UserProfilePage() 9 | { 10 | Title = "Profile"; 11 | } 12 | 13 | public override void OnViewModelSet() 14 | { 15 | base.OnViewModelSet(); 16 | 17 | var scrollView = new ScrollView 18 | { 19 | HorizontalOptions = LayoutOptions.FillAndExpand, 20 | VerticalOptions = LayoutOptions.Start, 21 | BackgroundColor = Color.White 22 | }; 23 | 24 | var layout = new StackLayout 25 | { 26 | HorizontalOptions = LayoutOptions.FillAndExpand, 27 | VerticalOptions = LayoutOptions.Start, 28 | Padding = 15, 29 | Spacing = 15 30 | }; 31 | 32 | var profileImage = new Image 33 | { 34 | WidthRequest = 300, 35 | HeightRequest = 300, 36 | HorizontalOptions = LayoutOptions.Center, 37 | VerticalOptions = LayoutOptions.Start, 38 | Source = ImageSource.FromResource(ViewModel.ImageName) 39 | }; 40 | 41 | var nameLabel = new Label 42 | { 43 | FontSize = 20, 44 | HorizontalOptions = LayoutOptions.Center, 45 | VerticalOptions = LayoutOptions.Start, 46 | HorizontalTextAlignment = TextAlignment.Center, 47 | TextColor = Color.Black 48 | }; 49 | BindToViewModel(nameLabel, Label.TextProperty, x => x.Name); 50 | 51 | layout.Children.Add(profileImage); 52 | layout.Children.Add(nameLabel); 53 | scrollView.Content = layout; 54 | 55 | Content = new StackLayout 56 | { 57 | HorizontalOptions = LayoutOptions.FillAndExpand, 58 | VerticalOptions = LayoutOptions.Start, 59 | Padding = 0, 60 | Spacing = 0, 61 | Children = 62 | { 63 | scrollView 64 | } 65 | }; 66 | } 67 | 68 | protected override void OnAppearing() 69 | { 70 | base.OnAppearing(); 71 | 72 | ToolbarItems.Clear(); 73 | ToolbarItems.Add(new ToolbarItem 74 | { 75 | Text = "Edit", 76 | Command = ViewModel.OpenFormCommand 77 | }); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Modules/Users/Profile/UserProfileViewModel.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using MvvmNano; 3 | using XamarinReduxDemo.Core.Shared; 4 | using XamarinReduxDemo.Store; 5 | 6 | namespace XamarinReduxDemo.Core.Modules.Users 7 | { 8 | public class UserProfileViewModel : ViewModelBase 9 | { 10 | private UserId _userId; 11 | 12 | private string _name; 13 | public string Name 14 | { 15 | get => _name; 16 | set { _name = value; NotifyPropertyChanged(); } 17 | } 18 | 19 | public string ImageName { get; private set; } 20 | 21 | private MvvmNanoCommand _openFormCommand; 22 | public MvvmNanoCommand OpenFormCommand 23 | => _openFormCommand ?? 24 | (_openFormCommand = new MvvmNanoCommand(OnOpenForm)); 25 | 26 | public UserProfileViewModel(IStore store) : base(store) 27 | { 28 | } 29 | 30 | public override void Initialize(UserId userId) 31 | { 32 | _userId = userId; 33 | 34 | ConnectToStore(state => state.Users.SingleOrDefault(u => Equals(u.Id, _userId)), user => 35 | { 36 | if (user == null) 37 | { 38 | return; 39 | } 40 | 41 | Name = user.Name; 42 | ImageName = user.Id.Item + ".jpg"; 43 | }); 44 | } 45 | 46 | private void OnOpenForm() 47 | { 48 | NavigateTo(_userId); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Resources/1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/XamarinReduxDemo.Core/Resources/1.jpg -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Resources/2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/XamarinReduxDemo.Core/Resources/2.jpg -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Resources/3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/XamarinReduxDemo.Core/Resources/3.jpg -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Shared/SmartCollection.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Collections.ObjectModel; 3 | using System.Collections.Specialized; 4 | using System.ComponentModel; 5 | 6 | namespace XamarinReduxDemo.Core.Shared 7 | { 8 | // Kudos to Jehof: https://stackoverflow.com/a/13303245 9 | public class SmartCollection : ObservableCollection 10 | { 11 | public SmartCollection() 12 | { 13 | } 14 | 15 | public SmartCollection(IEnumerable collection) 16 | : base(collection) 17 | { 18 | } 19 | 20 | public SmartCollection(List list) 21 | : base(list) 22 | { 23 | } 24 | 25 | public void AddRange(IEnumerable range) 26 | { 27 | foreach (var item in range) 28 | { 29 | Items.Add(item); 30 | } 31 | 32 | OnPropertyChanged(new PropertyChangedEventArgs("Count")); 33 | OnPropertyChanged(new PropertyChangedEventArgs("Item[]")); 34 | OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 35 | } 36 | 37 | public void Reset(IEnumerable range) 38 | { 39 | Items.Clear(); 40 | 41 | AddRange(range); 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/Shared/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using MvvmNano; 4 | using XamarinReduxDemo.Store; 5 | 6 | namespace XamarinReduxDemo.Core.Shared 7 | { 8 | public abstract class ViewModelBase : MvvmNanoViewModel 9 | { 10 | private readonly IList _disposables; 11 | 12 | protected readonly IStore Store; 13 | 14 | protected ViewModelBase(IStore store) 15 | { 16 | _disposables = new List(); 17 | 18 | Store = store; 19 | } 20 | 21 | protected void ConnectToStore( 22 | Func selector, 23 | Action callback 24 | ) 25 | { 26 | var subscription = Store.Subscribe( 27 | SubscriptionType.NewSelectionSubscription( 28 | selector, 29 | callback 30 | ) 31 | ); 32 | _disposables.Add(subscription); 33 | 34 | var selection = selector(Store.CurrentState); 35 | callback(selection); 36 | } 37 | 38 | public override void Dispose() 39 | { 40 | base.Dispose(); 41 | 42 | foreach (var disposable in _disposables) 43 | { 44 | disposable?.Dispose(); 45 | } 46 | } 47 | } 48 | 49 | public abstract class ViewModelBase : MvvmNanoViewModel 50 | { 51 | private readonly IList _disposables; 52 | 53 | protected readonly IStore Store; 54 | 55 | protected ViewModelBase(IStore store) 56 | { 57 | _disposables = new List(); 58 | 59 | Store = store; 60 | } 61 | 62 | protected void ConnectToStore( 63 | Func selector, 64 | Action callback 65 | ) 66 | { 67 | var subscription = Store.Subscribe( 68 | SubscriptionType.NewSelectionSubscription( 69 | selector, 70 | callback 71 | ) 72 | ); 73 | _disposables.Add(subscription); 74 | 75 | var selection = selector(Store.CurrentState); 76 | callback(selection); 77 | } 78 | 79 | public override void Dispose() 80 | { 81 | base.Dispose(); 82 | 83 | foreach (var disposable in _disposables) 84 | { 85 | disposable?.Dispose(); 86 | } 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Core/XamarinReduxDemo.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 2.jpg 25 | 26 | 27 | 3.jpg 28 | 29 | 30 | 1.jpg 31 | 32 | 33 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Store/Actions.fs: -------------------------------------------------------------------------------- 1 | namespace XamarinReduxDemo.Store 2 | 3 | type StoreAction = 4 | | CityRemoved of City 5 | | UserUpdated of User 6 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Store/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace XamarinReduxDemo.Store 2 | 3 | type CityId = CityId of int 4 | type City = 5 | { Id : CityId 6 | Name : string } 7 | 8 | type UserId = UserId of int 9 | type User = 10 | { Id : UserId 11 | Name : string } 12 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Store/Reducers.fs: -------------------------------------------------------------------------------- 1 | module XamarinReduxDemo.Store.RootReducer 2 | 3 | let reduceAppState action state = 4 | match action with 5 | | CityRemoved removedCity -> 6 | { state with 7 | Cities = 8 | state.Cities 9 | |> Array.filter (fun c -> c.Id <> removedCity.Id) } 10 | | UserUpdated updatedUser -> 11 | { state with 12 | Users = 13 | state.Users 14 | |> Array.map (fun u -> if u.Id = updatedUser.Id then updatedUser else u) } 15 | 16 | let Create() = reduceAppState 17 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Store/State.fs: -------------------------------------------------------------------------------- 1 | namespace XamarinReduxDemo.Store 2 | 3 | type AppState = 4 | { Cities : City array 5 | Users : User array } 6 | 7 | module InitialState = 8 | let Create() = 9 | // In Production you probably don't want to populate your 10 | // initial state with demo data – but for the purpose of 11 | // this demo that's a good place to start. 12 | { Cities = 13 | [| { Id = CityId(1); Name = "München" }; 14 | { Id = CityId(2); Name = "New York" }; 15 | { Id = CityId(3); Name = "Tokyo" } |] 16 | Users = 17 | [| { Id = UserId(1); Name = "Max" }; 18 | { Id = UserId(2); Name = "Zoe" }; 19 | { Id = UserId(3); Name = "Carl" } |] } 20 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Store/Store.fs: -------------------------------------------------------------------------------- 1 | namespace XamarinReduxDemo.Store 2 | 3 | open System 4 | 5 | type SubscriptionType<'TSelection> = 6 | | StateSubscription of Action 7 | | SelectionSubscription of Func * Action<'TSelection> 8 | | SelectionAndStateSubscription of Func * Action<'TSelection, AppState> 9 | 10 | type Subscription(unsubscribe:Subscription -> unit, callback:obj * AppState -> unit, selector:AppState -> obj) = 11 | member __.Callback = callback 12 | member __.Selector = selector 13 | interface IDisposable with 14 | member this.Dispose() = unsubscribe this 15 | 16 | type IStore = 17 | abstract CurrentState : AppState 18 | abstract Subscribe : SubscriptionType<'TSelection> -> Subscription 19 | abstract Unsubscribe : Subscription -> unit 20 | abstract Dispatch : StoreAction -> unit 21 | 22 | type Store(initialState:AppState, rootReducer:StoreAction -> AppState -> AppState) = 23 | let subscriptions = new ResizeArray() 24 | let mutable currentState = initialState 25 | let monitor = Object() 26 | let unsubscribe subscription = lock monitor (fun () -> 27 | subscriptions.Remove subscription |> ignore 28 | ) 29 | 30 | interface IStore with 31 | member __.CurrentState = currentState 32 | 33 | member __.Subscribe<'T>(subscriptionType:SubscriptionType<'T>) = 34 | let (callback:obj * AppState -> unit, selector:AppState -> obj) = 35 | match subscriptionType with 36 | | StateSubscription(c) -> 37 | (fun (_, state) -> c.Invoke(state)), 38 | fun _ -> id :> obj 39 | | SelectionSubscription(s, c) -> 40 | (fun (selection, _) -> c.Invoke((selection :?> 'T))), 41 | fun state -> s.Invoke state :> obj 42 | | SelectionAndStateSubscription(s, c) -> 43 | (fun (selection, state) -> c.Invoke((selection :?> 'T), state)), 44 | fun state -> s.Invoke state :> obj 45 | 46 | let subscription = new Subscription(unsubscribe, callback, selector) 47 | 48 | lock monitor (fun () -> 49 | subscriptions.Add subscription 50 | ) 51 | 52 | subscription 53 | 54 | member __.Unsubscribe subscription = unsubscribe subscription 55 | 56 | member __.Dispatch action = 57 | lock monitor (fun () -> 58 | let newState = currentState |> rootReducer action 59 | let oldState = currentState 60 | 61 | currentState <- newState 62 | 63 | subscriptions |> Seq.iter (fun subscription -> 64 | let newSelection = subscription.Selector newState 65 | let oldSelection = subscription.Selector oldState 66 | 67 | if newSelection <> oldSelection then 68 | subscription.Callback(newSelection, newState) 69 | ) 70 | ) 71 | -------------------------------------------------------------------------------- /XamarinReduxDemo.Store/XamarinReduxDemo.Store.fsproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | netstandard2.0 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /XamarinReduxDemo.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2012 4 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinReduxDemo.iOS", "iOS\XamarinReduxDemo.iOS.csproj", "{87F819BE-C59B-4BF6-8614-CD307F064366}" 5 | EndProject 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinReduxDemo.Droid", "Droid\XamarinReduxDemo.Droid.csproj", "{8414F9CC-CDCD-4E72-A214-3372CDD96A3A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "XamarinReduxDemo.Core", "XamarinReduxDemo.Core\XamarinReduxDemo.Core.csproj", "{73705DA3-6A6E-4B33-8902-CB8B258C10CF}" 9 | EndProject 10 | Project("{f2a71f9b-5d33-465a-a702-920d77279786}") = "XamarinReduxDemo.Store", "XamarinReduxDemo.Store\XamarinReduxDemo.Store.fsproj", "{F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Core", "Core", "{21F01E4A-6887-4B5A-BBB6-E5620B3A4239}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 19 | Release|iPhone = Release|iPhone 20 | Release|iPhoneSimulator = Release|iPhoneSimulator 21 | Debug|iPhone = Debug|iPhone 22 | EndGlobalSection 23 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 24 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 25 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 26 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Release|Any CPU.ActiveCfg = Release|iPhone 27 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Release|Any CPU.Build.0 = Release|iPhone 28 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 29 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 30 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Release|iPhone.ActiveCfg = Release|iPhone 31 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Release|iPhone.Build.0 = Release|iPhone 32 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 33 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 34 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Debug|iPhone.ActiveCfg = Debug|iPhone 35 | {87F819BE-C59B-4BF6-8614-CD307F064366}.Debug|iPhone.Build.0 = Debug|iPhone 36 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 37 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Debug|Any CPU.Build.0 = Debug|Any CPU 38 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Release|Any CPU.ActiveCfg = Release|Any CPU 39 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Release|Any CPU.Build.0 = Release|Any CPU 40 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 41 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 42 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Release|iPhone.ActiveCfg = Release|Any CPU 43 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Release|iPhone.Build.0 = Release|Any CPU 44 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 45 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 46 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Debug|iPhone.ActiveCfg = Debug|Any CPU 47 | {8414F9CC-CDCD-4E72-A214-3372CDD96A3A}.Debug|iPhone.Build.0 = Debug|Any CPU 48 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 49 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Debug|Any CPU.Build.0 = Debug|Any CPU 50 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Release|Any CPU.ActiveCfg = Release|Any CPU 51 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Release|Any CPU.Build.0 = Release|Any CPU 52 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 53 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 54 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Release|iPhone.ActiveCfg = Release|Any CPU 55 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Release|iPhone.Build.0 = Release|Any CPU 56 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 57 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 58 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Debug|iPhone.ActiveCfg = Debug|Any CPU 59 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF}.Debug|iPhone.Build.0 = Debug|Any CPU 60 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 61 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Debug|Any CPU.Build.0 = Debug|Any CPU 62 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Release|Any CPU.ActiveCfg = Release|Any CPU 63 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Release|Any CPU.Build.0 = Release|Any CPU 64 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 65 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 66 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Release|iPhone.ActiveCfg = Release|Any CPU 67 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Release|iPhone.Build.0 = Release|Any CPU 68 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 69 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 70 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Debug|iPhone.ActiveCfg = Debug|Any CPU 71 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A}.Debug|iPhone.Build.0 = Debug|Any CPU 72 | EndGlobalSection 73 | GlobalSection(NestedProjects) = preSolution 74 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF} = {21F01E4A-6887-4B5A-BBB6-E5620B3A4239} 75 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A} = {21F01E4A-6887-4B5A-BBB6-E5620B3A4239} 76 | EndGlobalSection 77 | EndGlobal 78 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/demo.gif -------------------------------------------------------------------------------- /iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using Foundation; 2 | using UIKit; 3 | using XamarinReduxDemo.Core; 4 | 5 | namespace XamarinReduxDemo.iOS 6 | { 7 | [Register("AppDelegate")] 8 | public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 9 | { 10 | public override bool FinishedLaunching(UIApplication app, NSDictionary options) 11 | { 12 | global::Xamarin.Forms.Forms.Init(); 13 | 14 | LoadApplication(new App()); 15 | 16 | return base.FinishedLaunching(app, options); 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /iOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "iphone", 5 | "size" : "20x20", 6 | "scale" : "2x" 7 | }, 8 | { 9 | "idiom" : "iphone", 10 | "size" : "20x20", 11 | "scale" : "3x" 12 | }, 13 | { 14 | "idiom" : "iphone", 15 | "size" : "29x29", 16 | "scale" : "2x" 17 | }, 18 | { 19 | "idiom" : "iphone", 20 | "size" : "29x29", 21 | "scale" : "3x" 22 | }, 23 | { 24 | "idiom" : "iphone", 25 | "size" : "40x40", 26 | "scale" : "2x" 27 | }, 28 | { 29 | "idiom" : "iphone", 30 | "size" : "40x40", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "idiom" : "iphone", 35 | "size" : "60x60", 36 | "scale" : "2x" 37 | }, 38 | { 39 | "idiom" : "iphone", 40 | "size" : "60x60", 41 | "scale" : "3x" 42 | }, 43 | { 44 | "idiom" : "ipad", 45 | "size" : "20x20", 46 | "scale" : "1x" 47 | }, 48 | { 49 | "idiom" : "ipad", 50 | "size" : "20x20", 51 | "scale" : "2x" 52 | }, 53 | { 54 | "idiom" : "ipad", 55 | "size" : "29x29", 56 | "scale" : "1x" 57 | }, 58 | { 59 | "idiom" : "ipad", 60 | "size" : "29x29", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "idiom" : "ipad", 65 | "size" : "40x40", 66 | "scale" : "1x" 67 | }, 68 | { 69 | "idiom" : "ipad", 70 | "size" : "40x40", 71 | "scale" : "2x" 72 | }, 73 | { 74 | "idiom" : "ipad", 75 | "size" : "76x76", 76 | "scale" : "1x" 77 | }, 78 | { 79 | "idiom" : "ipad", 80 | "size" : "76x76", 81 | "scale" : "2x" 82 | }, 83 | { 84 | "idiom" : "ipad", 85 | "size" : "83.5x83.5", 86 | "scale" : "2x" 87 | }, 88 | { 89 | "idiom" : "ios-marketing", 90 | "size" : "1024x1024", 91 | "scale" : "1x" 92 | }, 93 | { 94 | "size" : "24x24", 95 | "idiom" : "watch", 96 | "scale" : "2x", 97 | "role" : "notificationCenter", 98 | "subtype" : "38mm" 99 | }, 100 | { 101 | "size" : "27.5x27.5", 102 | "idiom" : "watch", 103 | "scale" : "2x", 104 | "role" : "notificationCenter", 105 | "subtype" : "42mm" 106 | }, 107 | { 108 | "size" : "29x29", 109 | "idiom" : "watch", 110 | "role" : "companionSettings", 111 | "scale" : "2x" 112 | }, 113 | { 114 | "size" : "29x29", 115 | "idiom" : "watch", 116 | "role" : "companionSettings", 117 | "scale" : "3x" 118 | }, 119 | { 120 | "size" : "40x40", 121 | "idiom" : "watch", 122 | "scale" : "2x", 123 | "role" : "appLauncher", 124 | "subtype" : "38mm" 125 | }, 126 | { 127 | "size" : "44x44", 128 | "idiom" : "watch", 129 | "scale" : "2x", 130 | "role" : "longLook", 131 | "subtype" : "42mm" 132 | }, 133 | { 134 | "size" : "86x86", 135 | "idiom" : "watch", 136 | "scale" : "2x", 137 | "role" : "quickLook", 138 | "subtype" : "38mm" 139 | }, 140 | { 141 | "size" : "98x98", 142 | "idiom" : "watch", 143 | "scale" : "2x", 144 | "role" : "quickLook", 145 | "subtype" : "42mm" 146 | }, 147 | { 148 | "idiom" : "mac", 149 | "size" : "16x16", 150 | "scale" : "1x" 151 | }, 152 | { 153 | "idiom" : "mac", 154 | "size" : "16x16", 155 | "scale" : "2x" 156 | }, 157 | { 158 | "idiom" : "mac", 159 | "size" : "32x32", 160 | "scale" : "1x" 161 | }, 162 | { 163 | "idiom" : "mac", 164 | "size" : "32x32", 165 | "scale" : "2x" 166 | }, 167 | { 168 | "idiom" : "mac", 169 | "size" : "128x128", 170 | "scale" : "1x" 171 | }, 172 | { 173 | "idiom" : "mac", 174 | "size" : "128x128", 175 | "scale" : "2x" 176 | }, 177 | { 178 | "idiom" : "mac", 179 | "size" : "256x256", 180 | "scale" : "1x" 181 | }, 182 | { 183 | "idiom" : "mac", 184 | "size" : "256x256", 185 | "scale" : "2x" 186 | }, 187 | { 188 | "idiom" : "mac", 189 | "size" : "512x512", 190 | "scale" : "1x" 191 | }, 192 | { 193 | "idiom" : "mac", 194 | "size" : "512x512", 195 | "scale" : "2x" 196 | } 197 | ], 198 | "info" : { 199 | "version" : 1, 200 | "author" : "xcode" 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /iOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /iOS/Info.plist: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | CFBundleDisplayName 6 | XamarinReduxDemo 7 | CFBundleName 8 | XamarinReduxDemo 9 | CFBundleIdentifier 10 | com.thomasbandt.XamarinReduxDemo 11 | CFBundleShortVersionString 12 | 1.0 13 | CFBundleVersion 14 | 1.0 15 | LSRequiresIPhoneOS 16 | 17 | MinimumOSVersion 18 | 8.0 19 | UIDeviceFamily 20 | 21 | 1 22 | 2 23 | 24 | UILaunchStoryboardName 25 | LaunchScreen 26 | UIRequiredDeviceCapabilities 27 | 28 | armv7 29 | 30 | UISupportedInterfaceOrientations 31 | 32 | UIInterfaceOrientationPortrait 33 | UIInterfaceOrientationLandscapeLeft 34 | UIInterfaceOrientationLandscapeRight 35 | 36 | UISupportedInterfaceOrientations~ipad 37 | 38 | UIInterfaceOrientationPortrait 39 | UIInterfaceOrientationPortraitUpsideDown 40 | UIInterfaceOrientationLandscapeLeft 41 | UIInterfaceOrientationLandscapeRight 42 | 43 | XSAppIconAssets 44 | Assets.xcassets/AppIcon.appiconset 45 | 46 | 47 | -------------------------------------------------------------------------------- /iOS/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /iOS/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace XamarinReduxDemo.iOS 9 | { 10 | public class Application 11 | { 12 | // This is the main entry point of the application. 13 | static void Main(string[] args) 14 | { 15 | // if you want to use a different Application Delegate class from "AppDelegate" 16 | // you can specify it here. 17 | UIApplication.Main(args, null, "AppDelegate"); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /iOS/Resources/cities@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/iOS/Resources/cities@2x.png -------------------------------------------------------------------------------- /iOS/Resources/users@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aspnetde/XamarinReduxDemo/3093aa699493ca4eafe52d8b49f23bfe7e528e84/iOS/Resources/users@2x.png -------------------------------------------------------------------------------- /iOS/XamarinReduxDemo.iOS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | iPhoneSimulator 7 | {87F819BE-C59B-4BF6-8614-CD307F064366} 8 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 9 | Exe 10 | XamarinReduxDemo.iOS 11 | XamarinReduxDemo.iOS 12 | Resources 13 | 14 | 15 | true 16 | full 17 | false 18 | bin\iPhoneSimulator\Debug 19 | DEBUG;ENABLE_TEST_CLOUD; 20 | prompt 21 | 4 22 | iPhone Developer 23 | true 24 | true 25 | true 26 | 36393 27 | None 28 | x86_64 29 | HttpClientHandler 30 | x86 31 | 32 | 33 | pdbonly 34 | true 35 | bin\iPhone\Release 36 | prompt 37 | 4 38 | iPhone Developer 39 | true 40 | Entitlements.plist 41 | SdkOnly 42 | ARM64 43 | HttpClientHandler 44 | x86 45 | 46 | 47 | pdbonly 48 | true 49 | bin\iPhoneSimulator\Release 50 | prompt 51 | 4 52 | iPhone Developer 53 | true 54 | None 55 | x86_64 56 | HttpClientHandler 57 | x86 58 | 59 | 60 | true 61 | full 62 | false 63 | bin\iPhone\Debug 64 | DEBUG;ENABLE_TEST_CLOUD; 65 | prompt 66 | 4 67 | iPhone Developer 68 | true 69 | true 70 | true 71 | true 72 | true 73 | Entitlements.plist 74 | 16560 75 | SdkOnly 76 | ARM64 77 | HttpClientHandler 78 | x86 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\Xamarin.iOS10\Xamarin.Forms.Core.dll 87 | 88 | 89 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\Xamarin.iOS10\Xamarin.Forms.Platform.dll 90 | 91 | 92 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\Xamarin.iOS10\Xamarin.Forms.Platform.iOS.dll 93 | 94 | 95 | ..\packages\Xamarin.Forms.2.5.0.121934\lib\Xamarin.iOS10\Xamarin.Forms.Xaml.dll 96 | 97 | 98 | ..\packages\MvvmNano.Core.3.2.0\lib\netstandard2.0\MvvmNano.Core.dll 99 | 100 | 101 | ..\packages\MvvmNano.TinyIoC.3.2.0\lib\netstandard2.0\MvvmNano.TinyIoC.dll 102 | 103 | 104 | ..\packages\MvvmNano.Forms.3.2.0\lib\netstandard2.0\MvvmNano.Forms.dll 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | {73705DA3-6A6E-4B33-8902-CB8B258C10CF} 129 | XamarinReduxDemo.Core 130 | 131 | 132 | {F69F0FAB-C5F5-4E1D-8101-C523BEB2651A} 133 | XamarinReduxDemo.Store 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /iOS/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | --------------------------------------------------------------------------------