├── .gitignore ├── Droid ├── Assets │ └── AboutAssets.txt ├── Droid.csproj ├── MainActivity.cs ├── Properties │ ├── AndroidManifest.xml │ └── AssemblyInfo.cs ├── Resources │ ├── AboutResources.txt │ ├── drawable-hdpi │ │ └── slideout.png │ ├── drawable-xxhdpi │ │ └── slideout.png │ ├── drawable │ │ ├── Splash.xml │ │ ├── icon.png │ │ └── reactive_logo.png │ ├── layout │ │ └── SplashScreen.axml │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ └── values │ │ ├── colors.xml │ │ └── styles.xml └── SplashScreen.cs ├── License.md ├── Presentation └── Reactive Extensions Examples Presentation.pdf ├── Reactive Extensions Overview.pdf ├── Reactive UI & Xamarin.Forms.pdf ├── ReactiveDotNetCoreApi ├── Controllers │ ├── ValuesController.cs │ └── WeatherForecastController.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── ReactiveDotNetCoreApi.csproj ├── Startup.cs ├── WeatherForecast.cs ├── appsettings.Development.json └── appsettings.json ├── ReactiveDotNetCoreConsole ├── Program.cs └── ReactiveDotNetCoreConsole.csproj ├── ReactiveExtensionExamples.sln ├── ReactiveExtensionExamples ├── App.cs ├── Extensions │ ├── IObservableExtensions.cs │ └── StyleExtensions.cs ├── FodyWeavers.xml ├── FodyWeavers.xsd ├── Models │ └── RssEntry.cs ├── ReactiveExtensionExamples.csproj ├── Services │ └── Api │ │ ├── DuckDuckGoServiceModel.cs │ │ ├── IDuckDuckGoApi.cs │ │ └── RssDownloader.cs ├── UserInterface │ ├── Cells │ │ └── RssEntryCell.cs │ └── Pages │ │ ├── Async.cs │ │ ├── AsyncEvent.cs │ │ ├── AsyncToObservable.cs │ │ ├── Buffer.cs │ │ ├── BufferWithWhere.cs │ │ ├── CombineLatest.cs │ │ ├── Delay.cs │ │ ├── DynamicData │ │ ├── DynamicDataDashboard.cs │ │ ├── FilterDynamicData.cs │ │ ├── SimpleDynamicData.cs │ │ ├── SortDynamicData.cs │ │ └── SourceCacheDynamicData.cs │ │ ├── Merge.cs │ │ ├── NavigationContainerPage.cs │ │ ├── NonReactivePage.cs │ │ ├── PageBase.cs │ │ ├── ReactiveUiColorSlider.cs │ │ ├── ReactiveUiEssentials.cs │ │ ├── ReactiveUiLogin.cs │ │ ├── ReactiveUiSearch.cs │ │ ├── Sample.cs │ │ ├── Scan.cs │ │ ├── SearchWithReactiveExtensions.cs │ │ ├── StandardSearch.cs │ │ ├── Throttle.cs │ │ └── TimerUpdaterObservableEvents.cs ├── Values │ └── Styles.cs └── ViewModels │ ├── ColorSlider.cs │ ├── DynamicData │ ├── FilterDynamicDataViewModel.cs │ ├── SimpleDynamicDataViewModel.cs │ ├── SortDynamicData.cs │ └── SourceCacheDynamicDataViewModel.cs │ ├── LoginCommand.cs │ ├── SearchViewModel.cs │ ├── ViewModelBase.cs │ └── XamarinEssentials.cs ├── ReadMe.md └── iOS ├── AppDelegate.cs ├── Entitlements.plist ├── ITunesArtwork ├── ITunesArtwork@2x ├── Info.plist ├── Main.cs ├── Resources ├── Default-568h@2x.png ├── Default-Portrait.png ├── Default-Portrait@2x.png ├── Default.png ├── Default@2x.png ├── Icon-60@3x.png ├── Icon-Small-40@3x.png ├── Icon-Small@3x.png ├── Images.xcassets │ └── AppIcons.appiconset │ │ ├── Contents.json │ │ ├── Icon-60@2x.png │ │ ├── Icon-76.png │ │ ├── Icon-76@2x.png │ │ ├── Icon-Small-40.png │ │ ├── Icon-Small-40@2x.png │ │ ├── Icon-Small.png │ │ ├── Icon-Small@2x.png │ │ ├── reactive_logo-29.png │ │ ├── reactive_logo-29@2x.png │ │ ├── reactive_logo-29@3x.png │ │ ├── reactive_logo-40.png │ │ ├── reactive_logo-40@2x.png │ │ ├── reactive_logo-40@3x.png │ │ ├── reactive_logo-60@2x.png │ │ ├── reactive_logo-60@3x.png │ │ ├── reactive_logo-76.png │ │ └── reactive_logo-76@2x.png ├── LaunchScreen.storyboard ├── iOS │ └── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── reactive_logo-29.png │ │ ├── reactive_logo-29@2x.png │ │ ├── reactive_logo-29@3x.png │ │ ├── reactive_logo-40.png │ │ ├── reactive_logo-40@2x.png │ │ ├── reactive_logo-40@3x.png │ │ ├── reactive_logo-60@2x.png │ │ ├── reactive_logo-60@3x.png │ │ ├── reactive_logo-76.png │ │ └── reactive_logo-76@2x.png ├── reactive_logo.png ├── reactive_logo@2x.png ├── reactive_logo@3x.png ├── slideout.png └── slideout@2x.png └── iOS.csproj /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.userprefs 8 | *.sln.docstates 9 | 10 | # Build results 11 | 12 | [Dd]ebug/ 13 | [Rr]elease/ 14 | x64/ 15 | build/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Enable "build/" folder in the NuGet Packages folder since NuGet packages use it for MSBuild targets 20 | !packages/*/build/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | *_i.c 27 | *_p.c 28 | *.ilk 29 | *.meta 30 | *.obj 31 | *.pch 32 | *.pdb 33 | *.pgc 34 | *.pgd 35 | *.rsp 36 | *.sbr 37 | *.tlb 38 | *.tli 39 | *.tlh 40 | *.tmp 41 | *.tmp_proj 42 | *.log 43 | *.vspscc 44 | *.vssscc 45 | .builds 46 | *.pidb 47 | *.log 48 | *.scc 49 | 50 | # Visual C++ cache files 51 | ipch/ 52 | *.aps 53 | *.ncb 54 | *.opensdf 55 | *.sdf 56 | *.cachefile 57 | 58 | # Visual Studio profiler 59 | *.psess 60 | *.vsp 61 | *.vspx 62 | 63 | # Guidance Automation Toolkit 64 | *.gpState 65 | 66 | # ReSharper is a .NET coding add-in 67 | _ReSharper*/ 68 | *.[Rr]e[Ss]harper 69 | 70 | # TeamCity is a build add-in 71 | _TeamCity* 72 | 73 | # DotCover is a Code Coverage Tool 74 | *.dotCover 75 | 76 | # NCrunch 77 | *.ncrunch* 78 | .*crunch*.local.xml 79 | 80 | # Installshield output folder 81 | [Ee]xpress/ 82 | 83 | # DocProject is a documentation generator add-in 84 | DocProject/buildhelp/ 85 | DocProject/Help/*.HxT 86 | DocProject/Help/*.HxC 87 | DocProject/Help/*.hhc 88 | DocProject/Help/*.hhk 89 | DocProject/Help/*.hhp 90 | DocProject/Help/Html2 91 | DocProject/Help/html 92 | 93 | # Click-Once directory 94 | publish/ 95 | 96 | # Publish Web Output 97 | *.Publish.xml 98 | *.pubxml 99 | 100 | # NuGet Packages Directory 101 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 102 | packages/ 103 | 104 | # Windows Azure Build Output 105 | csx 106 | *.build.csdef 107 | 108 | # Windows Store app package directory 109 | AppPackages/ 110 | 111 | # Others 112 | sql/ 113 | *.Cache 114 | ClientBin/ 115 | [Ss]tyle[Cc]op.* 116 | ~$* 117 | *~ 118 | *.dbmdl 119 | *.[Pp]ublish.xml 120 | *.pfx 121 | *.publishsettings 122 | 123 | # RIA/Silverlight projects 124 | Generated_Code/ 125 | 126 | # Backup & report files from converting an old project file to a newer 127 | # Visual Studio version. Backup files are not needed, because we have git ;-) 128 | _UpgradeReport_Files/ 129 | Backup*/ 130 | UpgradeLog*.XML 131 | UpgradeLog*.htm 132 | 133 | # SQL Server files 134 | App_Data/*.mdf 135 | App_Data/*.ldf 136 | 137 | # ========================= 138 | # Windows detritus 139 | # ========================= 140 | 141 | # Windows image file caches 142 | Thumbs.db 143 | ehthumbs.db 144 | 145 | # Folder config file 146 | Desktop.ini 147 | 148 | # Recycle Bin used on file shares 149 | $RECYCLE.BIN/ 150 | 151 | # Mac crap 152 | .DS_Store 153 | 154 | \.vs/ 155 | 156 | Droid/Resources/Resource\.designer\.cs 157 | -------------------------------------------------------------------------------- /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/Droid.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | AnyCPU 6 | {EFBA0AD7-5A72-4C68-AF49-83D382785DCF};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 7 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7} 8 | Library 9 | EightBot.ReactiveExtensionExamples.Droid 10 | Assets 11 | Resources 12 | Properties\AndroidManifest.xml 13 | Resource 14 | Resources\Resource.designer.cs 15 | True 16 | EightBot.ReactiveExtensionExamples.Droid 17 | v11.0 18 | 19 | 20 | true 21 | full 22 | false 23 | bin\Debug 24 | DEBUG; 25 | prompt 26 | 4 27 | None 28 | false 29 | Xamarin.Android.Net.AndroidClientHandler 30 | 31 | 32 | full 33 | true 34 | bin\Release 35 | prompt 36 | 4 37 | false 38 | false 39 | Xamarin.Android.Net.AndroidClientHandler 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 5.0.0.2401 56 | 57 | 58 | 18.0.7 59 | 60 | 61 | 18.0.7 62 | 63 | 64 | 1.7.2 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | {B54BEC10-F397-45B0-B080-E8E0292B2667} 106 | ReactiveExtensionExamples 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /Droid/MainActivity.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Android.App; 4 | using Android.Content; 5 | using Android.Content.PM; 6 | using Android.Runtime; 7 | using Android.Views; 8 | using Android.Widget; 9 | using Android.OS; 10 | 11 | namespace ReactiveExtensionExamples.Droid 12 | { 13 | [Activity ( 14 | Label = "Rx Examples", 15 | Icon = "@android:color/transparent", 16 | ConfigurationChanges = ConfigChanges.ScreenSize | ConfigChanges.Orientation, 17 | Theme = "@android:style/Theme.Holo.Light.DarkActionBar")] 18 | public class MainActivity : global::Xamarin.Forms.Platform.Android.FormsApplicationActivity 19 | { 20 | protected override void OnCreate (Bundle bundle) 21 | { 22 | base.OnCreate (bundle); 23 | 24 | global::Xamarin.Forms.Forms.Init (this, bundle); 25 | 26 | LoadApplication (new App ()); 27 | } 28 | } 29 | } 30 | 31 | -------------------------------------------------------------------------------- /Droid/Properties/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /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 ("ReactiveExtensionExamples.Droid")] 9 | [assembly: AssemblyDescription ("")] 10 | [assembly: AssemblyConfiguration ("")] 11 | [assembly: AssemblyCompany ("")] 12 | [assembly: AssemblyProduct ("")] 13 | [assembly: AssemblyCopyright ("Eight-Bot, Inc.")] 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 | 29 | -------------------------------------------------------------------------------- /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/slideout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/drawable-hdpi/slideout.png -------------------------------------------------------------------------------- /Droid/Resources/drawable-xxhdpi/slideout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/drawable-xxhdpi/slideout.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/Splash.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /Droid/Resources/drawable/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/drawable/icon.png -------------------------------------------------------------------------------- /Droid/Resources/drawable/reactive_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/drawable/reactive_logo.png -------------------------------------------------------------------------------- /Droid/Resources/layout/SplashScreen.axml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 16 | -------------------------------------------------------------------------------- /Droid/Resources/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /Droid/Resources/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /Droid/Resources/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Droid/Resources/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Droid/Resources/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Droid/Resources/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /Droid/Resources/values/colors.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | #909DA5 5 | -------------------------------------------------------------------------------- /Droid/Resources/values/styles.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 8 | -------------------------------------------------------------------------------- /Droid/SplashScreen.cs: -------------------------------------------------------------------------------- 1 |  2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | 7 | using Android.App; 8 | using Android.Content; 9 | using Android.OS; 10 | using Android.Runtime; 11 | using Android.Views; 12 | using Android.Widget; 13 | using System.Threading.Tasks; 14 | 15 | namespace ReactiveExtensionExamples.Droid 16 | { 17 | [Activity ( 18 | Label = "Rx Examples", 19 | Icon = "@mipmap/ic_launcher", 20 | Theme = "@style/Theme.Splash", 21 | MainLauncher = true, NoHistory = true)] 22 | public class SplashScreen : Activity 23 | { 24 | protected override void OnCreate (Bundle savedInstanceState) 25 | { 26 | base.OnCreate (savedInstanceState); 27 | 28 | this.StartActivity (typeof(MainActivity)); 29 | this.Finish (); 30 | } 31 | } 32 | } 33 | 34 | -------------------------------------------------------------------------------- /Presentation/Reactive Extensions Examples Presentation.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Presentation/Reactive Extensions Examples Presentation.pdf -------------------------------------------------------------------------------- /Reactive Extensions Overview.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Reactive Extensions Overview.pdf -------------------------------------------------------------------------------- /Reactive UI & Xamarin.Forms.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/Reactive UI & Xamarin.Forms.pdf -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/Controllers/ValuesController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using System.Reactive.Linq; 7 | using System.Net.Http; 8 | using System.Reactive.Threading.Tasks; 9 | using System.Reactive.Concurrency; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.AspNetCore.Http; 12 | using Newtonsoft.Json.Linq; 13 | using Newtonsoft.Json; 14 | using System.IO; 15 | 16 | namespace ReactiveDotNetCoreApi.Controllers 17 | { 18 | [Route("api/[controller]")] 19 | [ApiController] 20 | public class ValuesController : ControllerBase 21 | { 22 | private readonly ILogger _logger; 23 | 24 | static readonly HttpClient _client = new HttpClient(); 25 | 26 | public ValuesController(ILogger logger) 27 | { 28 | _logger = logger; 29 | } 30 | 31 | // GET api/values 32 | [HttpGet] 33 | public Task Get() 34 | { 35 | return Observable 36 | .FromAsync(() => _client.GetAsync("https://jsonplaceholder.typicode.com/todos")) 37 | .SubscribeOn(TaskPoolScheduler.Default) 38 | .Retry(5) 39 | .Timeout(TimeSpan.FromMilliseconds(80)) 40 | .Do(x => _logger?.LogInformation($"Message Successful? :{x.IsSuccessStatusCode}")) 41 | .SelectMany( 42 | async x => 43 | { 44 | JArray parsedTodos = null; 45 | 46 | using(var stream = await x.Content.ReadAsStreamAsync().ConfigureAwait(false)) 47 | using(var sr = new StreamReader(stream)) 48 | using(var jtr = new JsonTextReader(sr)) 49 | { 50 | parsedTodos = await JArray.LoadAsync(jtr).ConfigureAwait(false); 51 | } 52 | 53 | return parsedTodos.Select(pt => pt.SelectToken("title")).ToList(); 54 | }) 55 | .Select(x => new JsonResult(x)) 56 | .Catch(ex => Observable.Return(StatusCode(StatusCodes.Status503ServiceUnavailable))) 57 | .Catch(ex => Observable.Return(StatusCode(StatusCodes.Status500InternalServerError))) 58 | .ToTask(); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/Controllers/WeatherForecastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace ReactiveDotNetCoreApi.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class WeatherForecastController : ControllerBase 13 | { 14 | private static readonly string[] Summaries = new[] 15 | { 16 | "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" 17 | }; 18 | 19 | private readonly ILogger _logger; 20 | 21 | public WeatherForecastController (ILogger logger) 22 | { 23 | _logger = logger; 24 | } 25 | 26 | [HttpGet] 27 | public IEnumerable Get () 28 | { 29 | var rng = new Random(); 30 | return Enumerable.Range(1, 5).Select(index => new WeatherForecast 31 | { 32 | Date = DateTime.Now.AddDays(index), 33 | TemperatureC = rng.Next(-20, 55), 34 | Summary = Summaries[rng.Next(Summaries.Length)] 35 | }) 36 | .ToArray(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace ReactiveDotNetCoreApi 11 | { 12 | public class Program 13 | { 14 | public static void Main (string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder (string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:10052", 8 | "sslPort": 44373 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "weatherforecast", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "ReactiveDotNetCoreApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "weatherforecast", 24 | "applicationUrl": "https://localhost:52006;http://localhost:22473", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/ReactiveDotNetCoreApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace ReactiveDotNetCoreApi 15 | { 16 | public class Startup 17 | { 18 | public Startup (IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices (IServiceCollection services) 27 | { 28 | services.AddControllers(); 29 | } 30 | 31 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 32 | public void Configure (IApplicationBuilder app, IWebHostEnvironment env) 33 | { 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | } 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | app.UseRouting(); 42 | 43 | app.UseAuthorization(); 44 | 45 | app.UseEndpoints(endpoints => 46 | { 47 | endpoints.MapControllers(); 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/WeatherForecast.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace ReactiveDotNetCoreApi 4 | { 5 | public class WeatherForecast 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | public int TemperatureC { get; set; } 10 | 11 | public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); 12 | 13 | public string Summary { get; set; } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreConsole/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Reactive; 4 | using System.Reactive.Concurrency; 5 | using System.Reactive.Linq; 6 | using System.Threading.Tasks; 7 | using Spectre.Console; 8 | 9 | namespace ReactiveDotNetCoreConsole 10 | { 11 | class Program 12 | { 13 | private readonly static object matlock = new object(); 14 | 15 | static async Task Main (string[] args) 16 | { 17 | Console.WriteLine("Select an Example"); 18 | Console.WriteLine("1a: Publishing With Refcount - Bad"); 19 | Console.WriteLine("1b: Publishing With Refcount - Good"); 20 | Console.WriteLine("1c: Publishing With Refcount - Good"); 21 | Console.WriteLine("2a: Async with Ordering - Bad"); 22 | Console.WriteLine("2b: Async with Ordering - Good"); 23 | Console.WriteLine("3a: Limit Concurrency - Bad"); 24 | Console.WriteLine("3b: Limit Concurrency - Good"); 25 | Console.WriteLine("4a: Scheduler - Bad"); 26 | Console.WriteLine("4b: Scheduler - Good"); 27 | 28 | var selection = Console.ReadLine(); 29 | 30 | switch (selection) 31 | { 32 | case "1a": 33 | await PublishAndRefCountBadExample(); 34 | break; 35 | case "1b": 36 | await PublishAndRefCountGoodExample(); 37 | break; 38 | case "1c": 39 | await PublishAndRefCountAlternativeGoodExample(); 40 | break; 41 | case "2a": 42 | await AsyncOrderingBadExample(); 43 | break; 44 | case "2b": 45 | await AsyncOrderingGoodExample(); 46 | break; 47 | case "3a": 48 | await LimitConcurrencyBadExample(); 49 | break; 50 | case "3b": 51 | await LimitConcurrencyGoodExample(); 52 | break; 53 | case "4a": 54 | await SchedulerExampleBad(); 55 | break; 56 | case "4b": 57 | await SchedulerExampleGood(); 58 | break; 59 | default: 60 | break; 61 | } 62 | 63 | 64 | Console.WriteLine(); 65 | Console.WriteLine("Example Finished. Press Any key to continue..."); 66 | 67 | Console.ReadLine(); 68 | } 69 | 70 | public static async Task PublishAndRefCountBadExample () 71 | { 72 | var intervalObs = 73 | Observable 74 | .Interval(TimeSpan.FromSeconds(2)) 75 | .Do(i => WriteToConsoleWithColor((ConsoleColor)i, $"Processing Interval {i}")); 76 | 77 | var firstListener = 78 | intervalObs 79 | .Do(val => WriteToConsoleWithColor((ConsoleColor)val, $"First Listener Received: {val}")) 80 | .Subscribe(); 81 | 82 | var secondListener = 83 | intervalObs 84 | .Do(val => WriteToConsoleWithColor((ConsoleColor)val, $"Second Listener Received: {val}")) 85 | .Subscribe(); 86 | 87 | await Task.Delay(TimeSpan.FromSeconds(5)); 88 | 89 | firstListener.Dispose(); 90 | secondListener.Dispose(); 91 | } 92 | 93 | public static async Task PublishAndRefCountGoodExample () 94 | { 95 | var intervalObs = 96 | Observable 97 | .Interval(TimeSpan.FromSeconds(2)) 98 | .Do(i => WriteToConsoleWithColor((ConsoleColor)i, $"Processing Interval {i}")) 99 | .Publish() 100 | .RefCount(); 101 | 102 | var firstListener = 103 | intervalObs 104 | .Do(val => WriteToConsoleWithColor((ConsoleColor)val, $"First Listener Received: {val}")) 105 | .Subscribe(); 106 | 107 | var secondListener = 108 | intervalObs 109 | .Do(val => WriteToConsoleWithColor((ConsoleColor)val, $"Second Listener Received: {val}")) 110 | .Subscribe(); 111 | 112 | await Task.Delay(TimeSpan.FromSeconds(5)); 113 | 114 | firstListener.Dispose(); 115 | secondListener.Dispose(); 116 | } 117 | 118 | public static async Task PublishAndRefCountAlternativeGoodExample () 119 | { 120 | var intervalObs = 121 | Observable 122 | .Interval(TimeSpan.FromSeconds(2)) 123 | .Do(i => WriteToConsoleWithColor((ConsoleColor)i, $"Processing Interval {i}")) 124 | .Publish(); 125 | 126 | var firstListener = 127 | intervalObs 128 | .Do(val => WriteToConsoleWithColor((ConsoleColor)val, $"First Listener Received: {val}")) 129 | .Subscribe(); 130 | 131 | var secondListener = 132 | intervalObs 133 | .Do(val => WriteToConsoleWithColor((ConsoleColor)val, $"Second Listener Received: {val}")) 134 | .Subscribe(); 135 | 136 | intervalObs.Connect(); 137 | 138 | await Task.Delay(TimeSpan.FromSeconds(5)); 139 | 140 | firstListener.Dispose(); 141 | secondListener.Dispose(); 142 | } 143 | 144 | public static async Task AsyncOrderingBadExample () 145 | { 146 | var rng = new Random(); 147 | 148 | await Observable 149 | .Range(1, 10, TaskPoolScheduler.Default) 150 | .SelectMany( 151 | async range => 152 | { 153 | var delay = rng.Next(10, 300); 154 | await Task.Delay(delay); 155 | 156 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Finished after {delay}ms"); 157 | 158 | return range; 159 | }) 160 | .Do( 161 | range => 162 | { 163 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Received Notification at {DateTimeOffset.Now}"); 164 | }); 165 | } 166 | 167 | public static async Task AsyncOrderingGoodExample() 168 | { 169 | var rng = new Random(); 170 | 171 | await Observable 172 | .Range(1, 10, TaskPoolScheduler.Default) 173 | .Select( 174 | async range => 175 | { 176 | var delay = rng.Next(10, 300); 177 | await Task.Delay(delay); 178 | 179 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Finished after {delay}ms"); 180 | 181 | return range; 182 | }) 183 | .Concat() 184 | .Do( 185 | range => 186 | { 187 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Received Notification at {DateTimeOffset.Now}"); 188 | }); 189 | } 190 | 191 | public static async Task LimitConcurrencyBadExample () 192 | { 193 | var rng = new Random(); 194 | 195 | await Observable 196 | .Range(1, 10, TaskPoolScheduler.Default) 197 | .SelectMany( 198 | async range => 199 | { 200 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Started at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}"); 201 | var delay = rng.Next(10, 300); 202 | await Task.Delay(delay); 203 | 204 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Finished after {delay}ms"); 205 | 206 | return range; 207 | }) 208 | .Do( 209 | range => 210 | { 211 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Received Notification at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}"); 212 | }); 213 | } 214 | 215 | public static async Task LimitConcurrencyGoodExample () 216 | { 217 | var rng = new Random(); 218 | 219 | await Observable 220 | .Range(1, 10, TaskPoolScheduler.Default) 221 | .Select( 222 | range => 223 | Observable 224 | .DeferAsync( 225 | async ct => 226 | { 227 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Started at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}"); 228 | var delay = rng.Next(10, 300); 229 | await Task.Delay(delay); 230 | 231 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Finished after {delay}ms"); 232 | 233 | return Observable.Return(range); 234 | })) 235 | .Merge(1) 236 | .Do( 237 | range => 238 | { 239 | WriteToConsoleWithColor((ConsoleColor)range, $"{range} - Received Notification at {DateTimeOffset.Now.ToUnixTimeMilliseconds()}"); 240 | }); 241 | } 242 | 243 | public static Task SchedulerExampleBad () 244 | { 245 | var rng = new Random(); 246 | 247 | var subscription = Observable 248 | .Range(1, 10) 249 | .Repeat() 250 | .Do( 251 | range => 252 | { 253 | WriteToConsoleWithColor( 254 | (ConsoleColor)range, 255 | $"{range} - Received Notification at {DateTimeOffset.Now}"); 256 | }) 257 | .Subscribe(); 258 | 259 | subscription.Dispose(); 260 | 261 | return Task.CompletedTask; 262 | } 263 | 264 | public static Task SchedulerExampleGood () 265 | { 266 | var rng = new Random(); 267 | 268 | var subscription = Observable 269 | .Range(1, 10, TaskPoolScheduler.Default) 270 | .Repeat() 271 | .Do( 272 | range => 273 | { 274 | WriteToConsoleWithColor( 275 | (ConsoleColor)range, 276 | $"{range} - Received Notification at {DateTimeOffset.Now}"); 277 | }) 278 | .Subscribe(); 279 | 280 | subscription.Dispose(); 281 | 282 | return Task.CompletedTask; 283 | } 284 | 285 | private static void WriteToConsoleWithColor(ConsoleColor color, string value) 286 | { 287 | lock(matlock) 288 | { 289 | Console.ForegroundColor = color; 290 | Console.WriteLine(value); 291 | } 292 | } 293 | } 294 | } 295 | -------------------------------------------------------------------------------- /ReactiveDotNetCoreConsole/ReactiveDotNetCoreConsole.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio 15 3 | VisualStudioVersion = 15.0.27130.2010 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveExtensionExamples", "ReactiveExtensionExamples\ReactiveExtensionExamples.csproj", "{B54BEC10-F397-45B0-B080-E8E0292B2667}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "iOS", "iOS\iOS.csproj", "{ECB1239A-8DAD-4E69-A4D2-D879D46491F7}" 8 | EndProject 9 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Droid", "Droid\Droid.csproj", "{3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}" 10 | EndProject 11 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveDotNetCoreApi", "ReactiveDotNetCoreApi\ReactiveDotNetCoreApi.csproj", "{AFB538F3-98AD-479F-93F5-1DF2937F4D06}" 12 | EndProject 13 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Mobile", "Mobile", "{E9773837-BF86-46C1-AA2E-C1ECE465C855}" 14 | EndProject 15 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactiveDotNetCoreConsole", "ReactiveDotNetCoreConsole\ReactiveDotNetCoreConsole.csproj", "{0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}" 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | Debug|iPhoneSimulator = Debug|iPhoneSimulator 22 | Release|iPhoneSimulator = Release|iPhoneSimulator 23 | Debug|iPhone = Debug|iPhone 24 | Release|iPhone = Release|iPhone 25 | Ad-Hoc|iPhone = Ad-Hoc|iPhone 26 | AppStore|iPhone = AppStore|iPhone 27 | EndGlobalSection 28 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 29 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU 30 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU 31 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.AppStore|iPhone.ActiveCfg = Release|Any CPU 32 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.AppStore|iPhone.Build.0 = Release|Any CPU 33 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 34 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Debug|Any CPU.Build.0 = Debug|Any CPU 35 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Debug|iPhone.ActiveCfg = Debug|Any CPU 36 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Debug|iPhone.Build.0 = Debug|Any CPU 37 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 38 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 39 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Release|Any CPU.ActiveCfg = Release|Any CPU 40 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Release|Any CPU.Build.0 = Release|Any CPU 41 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Release|iPhone.ActiveCfg = Release|Any CPU 42 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Release|iPhone.Build.0 = Release|Any CPU 43 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 44 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 45 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU 46 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU 47 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.AppStore|iPhone.ActiveCfg = Debug|Any CPU 48 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.AppStore|iPhone.Build.0 = Debug|Any CPU 49 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 50 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Debug|Any CPU.Build.0 = Debug|Any CPU 51 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Debug|iPhone.ActiveCfg = Debug|Any CPU 52 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Debug|iPhone.Build.0 = Debug|Any CPU 53 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 54 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 55 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Release|Any CPU.ActiveCfg = Release|Any CPU 56 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Release|Any CPU.Build.0 = Release|Any CPU 57 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Release|iPhone.ActiveCfg = Release|Any CPU 58 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Release|iPhone.Build.0 = Release|Any CPU 59 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 60 | {B54BEC10-F397-45B0-B080-E8E0292B2667}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 61 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Ad-Hoc|iPhone.ActiveCfg = Ad-Hoc|iPhone 62 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Ad-Hoc|iPhone.Build.0 = Ad-Hoc|iPhone 63 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.AppStore|iPhone.ActiveCfg = AppStore|iPhone 64 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.AppStore|iPhone.Build.0 = AppStore|iPhone 65 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Debug|Any CPU.ActiveCfg = Debug|iPhoneSimulator 66 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Debug|Any CPU.Build.0 = Debug|iPhoneSimulator 67 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Debug|iPhone.ActiveCfg = Debug|iPhone 68 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Debug|iPhone.Build.0 = Debug|iPhone 69 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Debug|iPhoneSimulator.ActiveCfg = Debug|iPhoneSimulator 70 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Debug|iPhoneSimulator.Build.0 = Debug|iPhoneSimulator 71 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Release|Any CPU.ActiveCfg = Release|iPhoneSimulator 72 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Release|Any CPU.Build.0 = Release|iPhoneSimulator 73 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Release|iPhone.ActiveCfg = Release|iPhone 74 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Release|iPhone.Build.0 = Release|iPhone 75 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Release|iPhoneSimulator.ActiveCfg = Release|iPhoneSimulator 76 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7}.Release|iPhoneSimulator.Build.0 = Release|iPhoneSimulator 77 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 78 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Debug|Any CPU.Build.0 = Debug|Any CPU 79 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Release|Any CPU.ActiveCfg = Release|Any CPU 80 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Release|Any CPU.Build.0 = Release|Any CPU 81 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 82 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 83 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 84 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 85 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Debug|iPhone.ActiveCfg = Debug|Any CPU 86 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Debug|iPhone.Build.0 = Debug|Any CPU 87 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Release|iPhone.ActiveCfg = Release|Any CPU 88 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Release|iPhone.Build.0 = Release|Any CPU 89 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU 90 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU 91 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.AppStore|iPhone.ActiveCfg = Release|Any CPU 92 | {AFB538F3-98AD-479F-93F5-1DF2937F4D06}.AppStore|iPhone.Build.0 = Release|Any CPU 93 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 94 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Debug|Any CPU.Build.0 = Debug|Any CPU 95 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Release|Any CPU.ActiveCfg = Release|Any CPU 96 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Release|Any CPU.Build.0 = Release|Any CPU 97 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU 98 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU 99 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU 100 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Release|iPhoneSimulator.Build.0 = Release|Any CPU 101 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Debug|iPhone.ActiveCfg = Debug|Any CPU 102 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Debug|iPhone.Build.0 = Debug|Any CPU 103 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Release|iPhone.ActiveCfg = Release|Any CPU 104 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Release|iPhone.Build.0 = Release|Any CPU 105 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Ad-Hoc|iPhone.ActiveCfg = Release|Any CPU 106 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.Ad-Hoc|iPhone.Build.0 = Release|Any CPU 107 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.AppStore|iPhone.ActiveCfg = Release|Any CPU 108 | {0C92C783-D4CA-405C-9DA8-DF7A7B2AD562}.AppStore|iPhone.Build.0 = Release|Any CPU 109 | EndGlobalSection 110 | GlobalSection(NestedProjects) = preSolution 111 | {3B2BD6CA-BFCA-4640-940E-624FABAEF8C7} = {E9773837-BF86-46C1-AA2E-C1ECE465C855} 112 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7} = {E9773837-BF86-46C1-AA2E-C1ECE465C855} 113 | {B54BEC10-F397-45B0-B080-E8E0292B2667} = {E9773837-BF86-46C1-AA2E-C1ECE465C855} 114 | EndGlobalSection 115 | EndGlobal 116 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/App.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Refit; 3 | using Splat; 4 | using Xamarin.Forms; 5 | 6 | namespace ReactiveExtensionExamples 7 | { 8 | public class App : Application 9 | { 10 | public App () 11 | { 12 | Values.Styles.Initialize (); 13 | MainPage = new UserInterface.Pages.NavigationContainerPage (); 14 | } 15 | 16 | protected override void OnStart () 17 | { 18 | Locator 19 | .CurrentMutable 20 | .RegisterLazySingleton( 21 | () => RestService.For("https://api.duckduckgo.com"), 22 | typeof(Services.Api.IDuckDuckGoApi)); 23 | } 24 | 25 | protected override void OnSleep () 26 | { 27 | // Handle when your app sleeps 28 | } 29 | 30 | protected override void OnResume () 31 | { 32 | // Handle when your app resumes 33 | } 34 | } 35 | } 36 | 37 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/Extensions/IObservableExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using System.Reactive.Disposables; 4 | using System.Threading; 5 | using Xamarin.Forms; 6 | using ReactiveUI; 7 | using System.Reactive; 8 | 9 | namespace ReactiveExtensionExamples 10 | { 11 | public static class IObservableExtensions 12 | { 13 | public static TDisposable DisposeWith (this TDisposable observable, CompositeDisposable disposables) where TDisposable : class, IDisposable 14 | { 15 | if (observable != null) 16 | disposables.Add (observable); 17 | 18 | return observable; 19 | } 20 | 21 | public static IObservable SelectUnit(this IObservable observable) 22 | { 23 | return observable.Select(x => Unit.Default); 24 | } 25 | 26 | public static IObservable IsNotNull (this IObservable observable) 27 | where TValue : class 28 | { 29 | return observable.Where(x => x != null); 30 | } 31 | 32 | public static IDisposable NavigateTo (this IObservable observable, VisualElement ve, Func createPage, bool animated = true) 33 | { 34 | return observable 35 | .ObserveOn(RxApp.TaskpoolScheduler) 36 | .Select(x => createPage(x)) 37 | .ObserveOn(RxApp.MainThreadScheduler) 38 | .Do(async page => await ve.Navigation.PushAsync(page, animated)) 39 | .Subscribe(); 40 | } 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/Extensions/StyleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | using Xamarin.Forms; 4 | 5 | namespace ReactiveExtensionExamples.Extensions 6 | { 7 | public static class StyleExtensions 8 | { 9 | public static Style Extend (this Style style) 10 | { 11 | var newStyle = new Style (style.TargetType) { 12 | BasedOn = style 13 | }; 14 | return newStyle; 15 | } 16 | 17 | public static Style Set (this Style style, BindableProperty property, T value) 18 | { 19 | style.Setters.Add (new Setter () { Property = property, Value = value }); 20 | return style; 21 | } 22 | } 23 | } 24 | 25 | 26 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/FodyWeavers.xsd: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. 12 | 13 | 14 | 15 | 16 | A comma-separated list of error codes that can be safely ignored in assembly verification. 17 | 18 | 19 | 20 | 21 | 'false' to turn off automatic generation of the XML Schema file. 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/Models/RssEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace ReactiveExtensionExamples.Models 3 | { 4 | public class RssEntry 5 | { 6 | 7 | public string Id 8 | { 9 | get; 10 | set; 11 | } 12 | 13 | public string Author 14 | { 15 | get; 16 | set; 17 | } 18 | 19 | public string Category 20 | { 21 | get; 22 | set; 23 | } 24 | 25 | public string Content 26 | { 27 | get; 28 | set; 29 | } 30 | 31 | public DateTimeOffset Updated 32 | { 33 | get; 34 | set; 35 | } 36 | 37 | public string Title 38 | { 39 | get; 40 | set; 41 | } 42 | 43 | public bool New 44 | { 45 | get; 46 | set; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ReactiveExtensionExamples.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | netstandard2.1 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 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/Services/Api/DuckDuckGoServiceModel.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Reactive; 3 | 4 | namespace ReactiveExtensionExamples.Services.Api 5 | { 6 | /// 7 | /// Icon for related topic(s) or external site(s) 8 | /// 9 | public class DuckDuckGoIcon 10 | { 11 | /// 12 | /// URL of icon 13 | /// 14 | public string Url { get; set; } 15 | 16 | /// 17 | /// Height of icon (px) 18 | /// 19 | public string Height { get; set; } 20 | 21 | /// 22 | /// Width of icon (px) 23 | /// 24 | public string Width { get; set; } 25 | } 26 | 27 | /// 28 | /// Individual result returned from the query 29 | /// 30 | public class DuckDuckGoQueryResult 31 | { 32 | /// 33 | /// HTML link(s) to related topic(s) or external site(s) 34 | /// 35 | public string Result { get; set; } 36 | 37 | /// 38 | /// Icon associated with related topic(s) or FirstUrl 39 | /// 40 | public DuckDuckGoIcon Icon { get; set; } 41 | 42 | /// 43 | /// First URL in Result 44 | /// 45 | public string FirstUrl { get; set; } 46 | 47 | /// 48 | /// Text from first URL 49 | /// 50 | public string Text { get; set; } 51 | } 52 | 53 | /// 54 | /// Overal results from query 55 | /// 56 | public class DuckDuckGoSearchResult 57 | { 58 | /// 59 | /// Topic summary containing HTML 60 | /// 61 | public string Abstract { get; set; } 62 | 63 | /// 64 | /// Topic summary containing no HTML 65 | /// 66 | public string AbstractText { get; set; } 67 | 68 | /// 69 | /// Type of Answer, e.g. calc, color, digest, info, ip, iploc, phone, pw, rand, regexp, unicode, upc, or zip (see goodies & tech pages for examples). 70 | /// 71 | public string AnswerType { get; set; } 72 | 73 | /// 74 | /// Name of Abstract Source 75 | /// 76 | public string AbstractSource { get; set; } 77 | 78 | /// 79 | /// Dictionary definition (may differ from Abstract) 80 | /// 81 | public string Definition { get; set; } 82 | 83 | /// 84 | /// Name of Definition source 85 | /// 86 | public string DefinitionSource { get; set; } 87 | 88 | /// 89 | /// Name of topic that goes with Abstract 90 | /// 91 | public string Heading { get; set; } 92 | 93 | /// 94 | /// Link to image that goes with Abstract 95 | /// 96 | public string Image { get; set; } 97 | 98 | /// 99 | /// Array of internal links to related topics associated with Abstract 100 | /// 101 | public List RelatedTopics { get; set; } 102 | 103 | /// 104 | /// Response category, i.e. A (article), D (disambiguation), C (category), N (name), E (exclusive), or nothing. 105 | /// 106 | public string Type { get; set; } 107 | 108 | /// 109 | /// !bang redirect URL 110 | /// 111 | public string Redirect { get; set; } 112 | 113 | /// 114 | /// Deep link to expanded definition page in DefinitionSource 115 | /// 116 | public string DefinitionUrl { get; set; } 117 | 118 | /// 119 | /// Instant answer 120 | /// 121 | public string Answer { get; set; } 122 | 123 | /// 124 | /// Array of external links associated with Abstract 125 | /// 126 | public List Results { get; set; } 127 | 128 | /// 129 | /// Deep link to the expanded topic page in AbstractSource 130 | /// 131 | public string AbstractUrl { get; set; } 132 | } 133 | } -------------------------------------------------------------------------------- /ReactiveExtensionExamples/Services/Api/IDuckDuckGoApi.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Refit; 7 | using System.Threading; 8 | 9 | namespace ReactiveExtensionExamples.Services.Api 10 | { 11 | public interface IDuckDuckGoApi 12 | { 13 | [Get("/?q={query}&format=json")] 14 | Task Search(string query); 15 | 16 | [Get("/?q={query}&format=json")] 17 | Task Search(string query, CancellationToken cancellationToken); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/Services/Api/RssDownloader.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Refit; 7 | using System.Threading; 8 | using ReactiveExtensionExamples.Models; 9 | using System.Net.Http; 10 | using System.Xml.Linq; 11 | 12 | namespace ReactiveExtensionExamples.Services.Api 13 | { 14 | public static class RssDownloader 15 | { 16 | private static readonly HttpClient client = new HttpClient(); 17 | 18 | public static async Task> DownloadRss (string url, CancellationToken ct) 19 | { 20 | var rssStreamResponse = await client.GetAsync(url, ct).ConfigureAwait(false); 21 | 22 | if (!ct.IsCancellationRequested && rssStreamResponse.IsSuccessStatusCode) 23 | { 24 | var rssStream = await rssStreamResponse.Content.ReadAsStringAsync().ConfigureAwait(false); 25 | 26 | if (!ct.IsCancellationRequested) 27 | { 28 | return 29 | await Task.Run(() => 30 | { 31 | XNamespace ns = "http://www.w3.org/2005/Atom"; 32 | 33 | var entries = 34 | XDocument 35 | .Parse(rssStream) 36 | .Root 37 | .Descendants(ns + "entry"); 38 | 39 | return entries 40 | .Select(entry => 41 | new RssEntry 42 | { 43 | Id = entry?.Element(ns + "id")?.Value ?? string.Empty, 44 | Author = entry?.Element(ns + "author")?.Element(ns + "name")?.Value ?? string.Empty, 45 | Category = entry?.Element(ns + "category")?.Attribute("label")?.Value ?? string.Empty, 46 | Content = entry?.Element(ns + "content")?.Value ?? string.Empty, 47 | Updated = DateTimeOffset.Parse(entry?.Element(ns + "updated")?.Value), 48 | Title = entry?.Element(ns + "title")?.Value ?? string.Empty 49 | }) 50 | .ToList(); 51 | }, ct) 52 | .ConfigureAwait(false); 53 | } 54 | } 55 | 56 | return Enumerable.Empty(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Cells/RssEntryCell.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Linq; 3 | using ReactiveExtensionExamples.Models; 4 | using ReactiveExtensionExamples.ViewModels; 5 | using ReactiveUI; 6 | using ReactiveUI.XamForms; 7 | using Xamarin.Forms; 8 | 9 | namespace ReactiveExtensionExamples.UserInterface.Cells 10 | { 11 | class RssEntryCell : ReactiveContentView 12 | { 13 | Label newFlag; 14 | 15 | public RssEntryCell () 16 | { 17 | var stackLayout = new StackLayout 18 | { 19 | Padding = new Thickness(8d, 0d), 20 | Spacing = 4d 21 | }; 22 | 23 | newFlag = new Label 24 | { 25 | Text = "New", 26 | FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)), 27 | TextColor = Color.Accent, 28 | FontAttributes = FontAttributes.Italic, 29 | }; 30 | stackLayout.Children.Add(newFlag); 31 | 32 | var title = new Label 33 | { 34 | FontSize = Device.GetNamedSize(NamedSize.Medium, typeof(Label)), 35 | LineBreakMode = LineBreakMode.TailTruncation, 36 | MaxLines = 3, 37 | HeightRequest = 70, 38 | }; 39 | stackLayout.Children.Add(title); 40 | 41 | var category = new Label 42 | { 43 | FontSize = Device.GetNamedSize(NamedSize.Small, typeof(Label)), 44 | TextColor = Color.Gray, 45 | HeightRequest = 20, 46 | }; 47 | stackLayout.Children.Add(category); 48 | 49 | var updated = new Label 50 | { 51 | FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)), 52 | FontAttributes = FontAttributes.Italic, 53 | TextColor = Color.Gray, 54 | HeightRequest = 20, 55 | }; 56 | stackLayout.Children.Add(updated); 57 | 58 | var padding = 59 | new ContentView 60 | { 61 | BackgroundColor = Color.Silver, 62 | HeightRequest = 2, 63 | Margin = new Thickness(-8, 0), 64 | }; 65 | stackLayout.Children.Add(padding); 66 | 67 | Content = stackLayout; 68 | 69 | this.WhenAnyValue(x => x.ViewModel) 70 | .IsNotNull() 71 | .ObserveOn(RxApp.MainThreadScheduler) 72 | .Do( 73 | vm => 74 | { 75 | title.Text = vm.Title; 76 | category.Text = vm.Category; 77 | updated.Text = vm.Updated.ToLocalTime().ToString("dd MMM yyyy hh:mm tt"); 78 | }) 79 | .Subscribe(); 80 | 81 | this.WhenAnyValue(x => x.ViewModel.New) 82 | .ObserveOn(RxApp.MainThreadScheduler) 83 | .BindTo(this, x => x.newFlag.IsVisible); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/Async.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using Xamarin.Forms; 6 | 7 | namespace ReactiveExtensionExamples.UserInterface.Pages 8 | { 9 | public class Async : PageBase 10 | { 11 | Label outputLabel, calculationProgress; 12 | Button download; 13 | 14 | protected override void SetupUserInterface () 15 | { 16 | Title = "Rx - Async"; 17 | 18 | download = new Button{ Text = "Calculate" }; 19 | 20 | Content = new StackLayout { 21 | Padding = new Thickness(8d), 22 | Spacing = 16d, 23 | Children = { 24 | download, 25 | (calculationProgress = 26 | new Label { 27 | HorizontalTextAlignment = TextAlignment.Center, 28 | FontAttributes = FontAttributes.Italic, 29 | Text = "Next Value: " 30 | } 31 | ), 32 | (outputLabel = new Label { HorizontalTextAlignment = TextAlignment.Center, Text = "Calculation Result" }), 33 | 34 | } 35 | }; 36 | } 37 | 38 | protected override void SetupReactiveExtensions () 39 | { 40 | var random = new Random(DateTime.Now.Millisecond); 41 | 42 | var calculationObservable = 43 | Observable 44 | .Interval(TimeSpan.FromMilliseconds(random.Next(100, 300))) 45 | .Zip( 46 | Observable.Range(random.Next(1, 5), random.Next(2, 7)), 47 | (t, r) => (long)r 48 | ) 49 | .Scan((previous, current) => previous * current * (long)(random.Next(1, 35))) 50 | .Do(val => Device.BeginInvokeOnMainThread(() => calculationProgress.Text = string.Format("Next Value: {0}", val))); 51 | 52 | Observable 53 | .FromEventPattern (x => download.Clicked += x, x => download.Clicked -= x) 54 | .Subscribe (async args => { 55 | try { 56 | Device.BeginInvokeOnMainThread(() => { 57 | download.IsEnabled = false; 58 | outputLabel.Text = "Starting Calculation"; 59 | }); 60 | 61 | var result = await calculationObservable; 62 | 63 | Device.BeginInvokeOnMainThread(() => 64 | outputLabel.Text = string.Format("Calculation Complete: {0}", result) 65 | ); 66 | 67 | } finally { 68 | Device.BeginInvokeOnMainThread(() => 69 | download.IsEnabled = true 70 | ); 71 | } 72 | }) 73 | .DisposeWith(SubscriptionDisposables); 74 | } 75 | } 76 | } 77 | 78 | 79 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/AsyncEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using ReactiveUI; 6 | using Xamarin.Forms; 7 | 8 | namespace ReactiveExtensionExamples.UserInterface.Pages 9 | { 10 | public class AsyncEvent : PageBase 11 | { 12 | Label outputLabel; 13 | Button calculate, stop; 14 | 15 | IDisposable calculationSubscription; 16 | 17 | protected override void SetupUserInterface () 18 | { 19 | Title = "Rx - Async Events"; 20 | 21 | calculate = new Button{ Text = "Calculate" }; 22 | 23 | stop = new Button{ Text = "STOP" }; 24 | 25 | Content = new StackLayout { 26 | Padding = new Thickness(8d), 27 | Spacing = 16d, 28 | Children = { 29 | calculate, 30 | stop, 31 | (outputLabel = new Label { 32 | HorizontalTextAlignment = TextAlignment.Center, 33 | Text = "Let's calcuNOW, not calcuLATEr" 34 | }) 35 | } 36 | }; 37 | } 38 | 39 | protected override void SetupReactiveExtensions () 40 | { 41 | var stopClickedObservable = 42 | Observable 43 | .FromEventPattern (x => stop.Clicked += x, x => stop.Clicked -= x) 44 | .Do(args => System.Diagnostics.Debug.WriteLine("Button 2 Clicked")) 45 | .FirstAsync (); 46 | 47 | var calculationObservable = 48 | Observable 49 | .Interval (TimeSpan.FromMilliseconds (250)) 50 | .Do (val => System.Diagnostics.Debug.WriteLine ("Next Value: {0}", val)) 51 | .Scan ((previous, current) => previous + current); 52 | 53 | Observable 54 | .FromEventPattern (x => calculate.Clicked += x, x => calculate.Clicked -= x) 55 | .Subscribe (async args => { 56 | try { 57 | Device.BeginInvokeOnMainThread(() => calculate.IsEnabled = false); 58 | 59 | //Start Calculating 60 | calculationSubscription = 61 | calculationObservable 62 | .Subscribe(val => 63 | Device.BeginInvokeOnMainThread(() => 64 | outputLabel.Text = string.Format("Calculation Value: {0}", val) 65 | ) 66 | ); 67 | 68 | //This will only get the first click of the button after we start listening 69 | await stopClickedObservable; 70 | 71 | calculationSubscription?.Dispose(); 72 | 73 | Device.BeginInvokeOnMainThread(() => 74 | outputLabel.Text = string.Format("Clicked Stop at " + DateTime.Now) 75 | ); 76 | } finally { 77 | Device.BeginInvokeOnMainThread(() => calculate.IsEnabled = true); 78 | } 79 | }) 80 | .DisposeWith(SubscriptionDisposables); 81 | } 82 | 83 | protected override void OnDisappearing () 84 | { 85 | base.OnDisappearing (); 86 | 87 | calculationSubscription?.Dispose(); 88 | } 89 | } 90 | } 91 | 92 | 93 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/AsyncToObservable.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using System.Threading.Tasks; 6 | using Xamarin.Forms; 7 | 8 | namespace ReactiveExtensionExamples.UserInterface.Pages 9 | { 10 | public class AsyncToObservable : PageBase 11 | { 12 | Label outputLabel; 13 | Button button1; 14 | ActivityIndicator loading; 15 | 16 | protected override void SetupUserInterface () 17 | { 18 | Title = "Rx - Async to Observable"; 19 | 20 | button1 = new Button{ Text = "Call Service" }; 21 | 22 | loading = new ActivityIndicator { }; 23 | 24 | Content = new StackLayout { 25 | Padding = new Thickness(8d), 26 | Spacing = 16d, 27 | Children = { 28 | button1, 29 | (outputLabel = new Label { HorizontalTextAlignment = TextAlignment.Center, Text = "" }), 30 | loading 31 | } 32 | }; 33 | } 34 | 35 | protected override void SetupReactiveExtensions () 36 | { 37 | Observable 38 | .FromEventPattern (x => button1.Clicked += x, x => button1.Clicked -= x) 39 | .Subscribe (async args => { 40 | 41 | Device.BeginInvokeOnMainThread(() => { 42 | outputLabel.TextColor = Color.Black; 43 | outputLabel.Text = "Starting Calculation"; 44 | loading.IsRunning = true; 45 | }); 46 | 47 | try { 48 | var result = 49 | await Observable 50 | .FromAsync(() => PerformCalculationAsync()) 51 | .Timeout(TimeSpan.FromMilliseconds(300)) 52 | .Retry(5) 53 | .Catch(tex => Observable.Return(-1)) 54 | .Catch(ex => Observable.Return(-100)); 55 | 56 | Device.BeginInvokeOnMainThread(() => { 57 | outputLabel.Text = 58 | result >= 0 59 | ? string.Format("Calculation Complete: {0}", result) 60 | : result == -100 61 | ? "Listen, things went really bad." + Environment.NewLine + "Reconsider your life choices" 62 | : "Bummer, it looks like your calculation failed"; 63 | 64 | if(result < 0) 65 | outputLabel.TextColor = Color.Red; 66 | }); 67 | } finally { 68 | Device.BeginInvokeOnMainThread(() => loading.IsRunning = false); 69 | } 70 | }) 71 | .DisposeWith(SubscriptionDisposables); 72 | } 73 | 74 | //Imagine this is a faux web service or similar 75 | async Task PerformCalculationAsync (){ 76 | var random = new Random (DateTime.Now.Millisecond); 77 | 78 | var delayTime = random.Next (150, 500); 79 | 80 | System.Diagnostics.Debug.WriteLine ("Delaying {0}", delayTime); 81 | 82 | await Task.Delay (delayTime); 83 | 84 | var calcedValue = random.Next (1, 10); 85 | 86 | System.Diagnostics.Debug.WriteLine ("Calced Value {0}", calcedValue); 87 | 88 | if (calcedValue % 2 == 0) 89 | throw new Exception ("Even number are not allowed"); 90 | 91 | return calcedValue; 92 | } 93 | } 94 | } 95 | 96 | 97 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/Buffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Concurrency; 4 | using System.Reactive.Disposables; 5 | using System.Reactive.Linq; 6 | using Xamarin.Forms; 7 | 8 | namespace ReactiveExtensionExamples.UserInterface.Pages 9 | { 10 | public class Buffer : PageBase 11 | { 12 | Entry textEntry; 13 | StackLayout lastEntries; 14 | 15 | protected override void SetupUserInterface () 16 | { 17 | Title = "Rx - Buffer"; 18 | 19 | Content = new StackLayout { 20 | Padding = new Thickness(8d), 21 | Spacing = 16d, 22 | Children = { 23 | (textEntry = new Entry{ Placeholder = "Enter Some Text" }), 24 | new ScrollView { 25 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, 26 | Content = (lastEntries = new StackLayout{ 27 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand 28 | }) 29 | } 30 | } 31 | }; 32 | } 33 | 34 | protected override void SetupReactiveExtensions () 35 | { 36 | Observable 37 | .FromEventPattern, TextChangedEventArgs> ( 38 | x => textEntry.TextChanged += x, 39 | x => textEntry.TextChanged -= x) 40 | .Buffer (TimeSpan.FromSeconds (3), TaskPoolScheduler.Default) 41 | .Select(argsList => 42 | string.Join( 43 | Environment.NewLine, 44 | argsList.Select(args => args.EventArgs.NewTextValue).Reverse().ToList() 45 | ) 46 | ) 47 | .Subscribe (text => { 48 | Device.BeginInvokeOnMainThread(() => { 49 | lastEntries.Children.Insert( 50 | 0, 51 | new Label { Text = text } 52 | ); 53 | 54 | lastEntries.Children 55 | .Insert( 56 | 1, 57 | new Label { 58 | Text = string.Format("Received at {0:H:mm:ss}", DateTime.Now), 59 | FontAttributes = FontAttributes.Italic, 60 | FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)), 61 | TextColor = Color.Gray 62 | }); 63 | 64 | lastEntries.Children 65 | .Insert( 66 | 2, 67 | new BoxView { BackgroundColor = Color.Gray, HeightRequest = 2d }); 68 | }); 69 | }) 70 | .DisposeWith(SubscriptionDisposables); 71 | } 72 | } 73 | } 74 | 75 | 76 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/BufferWithWhere.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Concurrency; 4 | using System.Reactive.Disposables; 5 | using System.Reactive.Linq; 6 | using Xamarin.Forms; 7 | 8 | namespace ReactiveExtensionExamples.UserInterface.Pages 9 | { 10 | public class BufferWithWhere : PageBase 11 | { 12 | Entry textEntry; 13 | StackLayout lastEntries; 14 | 15 | protected override void SetupUserInterface () 16 | { 17 | Title = "Rx - Buffer with Filtering Where"; 18 | 19 | Content = new StackLayout { 20 | Padding = new Thickness(8d), 21 | Spacing = 16d, 22 | Children = { 23 | (textEntry = new Entry{ Placeholder = "Enter Some Text" }), 24 | new ScrollView { 25 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, 26 | Content = (lastEntries = new StackLayout{ 27 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand 28 | }) 29 | } 30 | } 31 | }; 32 | } 33 | 34 | protected override void SetupReactiveExtensions () 35 | { 36 | Observable 37 | .FromEventPattern, TextChangedEventArgs> ( 38 | x => textEntry.TextChanged += x, 39 | x => textEntry.TextChanged -= x 40 | ) 41 | .Buffer (TimeSpan.FromSeconds (3), TaskPoolScheduler.Default) 42 | .Where (argsList => argsList?.Any () ?? false) 43 | .Select(argsList => 44 | string.Join( 45 | Environment.NewLine, 46 | argsList.Select(args => args.EventArgs.NewTextValue).Reverse().ToList() 47 | )) 48 | .Subscribe (text => { 49 | Device.BeginInvokeOnMainThread(() => { 50 | lastEntries.Children.Insert( 51 | 0, 52 | new Label { Text = text } 53 | ); 54 | 55 | lastEntries.Children 56 | .Insert( 57 | 1, 58 | new Label { 59 | Text = string.Format("Received at {0:H:mm:ss}", DateTime.Now), 60 | FontAttributes = FontAttributes.Italic, 61 | FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)), 62 | TextColor = Color.Gray 63 | }); 64 | 65 | lastEntries.Children 66 | .Insert( 67 | 2, 68 | new BoxView { BackgroundColor = Color.Gray, HeightRequest = 2d }); 69 | }); 70 | }) 71 | .DisposeWith(SubscriptionDisposables); 72 | } 73 | } 74 | } 75 | 76 | 77 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/CombineLatest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using Xamarin.Forms; 6 | 7 | namespace ReactiveExtensionExamples.UserInterface.Pages 8 | { 9 | public class CombineLatest: PageBase 10 | { 11 | BoxView colorDisplay; 12 | 13 | Slider red, green, blue; 14 | 15 | public CombineLatest () 16 | { 17 | Title = "Combine Latest"; 18 | 19 | Content = new StackLayout { 20 | Padding = new Thickness(40d), 21 | Children = { 22 | (colorDisplay = new BoxView{ HeightRequest = 250 }), 23 | 24 | new Label{ Text = "Red"}, 25 | (red = new Slider(0, 255, 0)), 26 | 27 | new Label{ Text = "Green"}, 28 | (green = new Slider(0, 255, 0)), 29 | 30 | new Label{ Text = "Blue"}, 31 | (blue = new Slider(0, 255, 0)) 32 | } 33 | }; 34 | } 35 | 36 | 37 | protected override void SetupReactiveExtensions () 38 | { 39 | base.SetupReactiveExtensions (); 40 | 41 | Observable 42 | .CombineLatest ( 43 | red 44 | .Events() 45 | .ValueChanged 46 | .Select(result => (int)result.NewValue), 47 | green 48 | .Events() 49 | .ValueChanged 50 | .Select(result => (int)result.NewValue) 51 | .StartWith(0), 52 | blue 53 | .Events() 54 | .ValueChanged 55 | .Select(result => (int)result.NewValue) 56 | .StartWith(0), 57 | (r, g, b) => Color.FromRgb (r, g, b) 58 | ) 59 | .Do(x => System.Diagnostics.Debug.WriteLine($"current color: {x}")) 60 | .Subscribe (color => { 61 | Device.BeginInvokeOnMainThread (() => colorDisplay.BackgroundColor = color); 62 | }) 63 | .DisposeWith (SubscriptionDisposables); 64 | } 65 | } 66 | } 67 | 68 | 69 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/Delay.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Disposables; 3 | using System.Reactive.Linq; 4 | using Xamarin.Forms; 5 | 6 | namespace ReactiveExtensionExamples.UserInterface.Pages 7 | { 8 | public class Delay : PageBase 9 | { 10 | Entry textEntry; 11 | StackLayout lastEntries; 12 | 13 | protected override void SetupUserInterface () 14 | { 15 | Title = "Rx - Delay"; 16 | 17 | Content = new StackLayout { 18 | Padding = new Thickness(8d), 19 | Spacing = 16d, 20 | Children = { 21 | (textEntry = new Entry{ Placeholder = "Enter Some Text" }), 22 | new ScrollView { 23 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, 24 | Content = (lastEntries = new StackLayout{ 25 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand 26 | }) 27 | } 28 | } 29 | }; 30 | } 31 | 32 | protected override void SetupReactiveExtensions () 33 | { 34 | Observable 35 | .FromEventPattern, TextChangedEventArgs> ( 36 | x => textEntry.TextChanged += x, 37 | x => textEntry.TextChanged -= x) 38 | //MTS : The PM said 3 seconds was a great delay 39 | .Delay (TimeSpan.FromSeconds (3)) 40 | 41 | .Select(args => args.EventArgs.NewTextValue) 42 | .StartWith(string.Empty) 43 | .Subscribe(text => { 44 | Device.BeginInvokeOnMainThread(() => { 45 | lastEntries.Children 46 | .Insert( 47 | 0, 48 | new Label { Text = text }); 49 | lastEntries.Children 50 | .Insert( 51 | 1, 52 | new Label 53 | { 54 | Text = string.Format("Received at {0:H:mm:ss}", DateTime.Now), 55 | FontAttributes = FontAttributes.Italic, 56 | FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)), 57 | TextColor = Color.Gray 58 | }); 59 | lastEntries.Children 60 | .Insert( 61 | 2, 62 | new BoxView { BackgroundColor = Color.Gray, HeightRequest = 2d }); 63 | }); 64 | }) 65 | .DisposeWith(SubscriptionDisposables); 66 | } 67 | } 68 | } 69 | 70 | 71 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/DynamicData/DynamicDataDashboard.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | 4 | namespace ReactiveExtensionExamples.UserInterface.Pages.DynamicData 5 | { 6 | public class DynamicDataDashboard : PageBase 7 | { 8 | ScrollView _mainScroll; 9 | 10 | StackLayout _mainLayout; 11 | 12 | Button _simpleDynamicData, _filterDynamicData, _sortDynamicData, _sourceCacheDynamicData; 13 | 14 | public DynamicDataDashboard () 15 | { 16 | } 17 | 18 | protected override void SetupUserInterface () 19 | { 20 | _simpleDynamicData = 21 | new Button 22 | { 23 | Text = "Simple" 24 | }; 25 | 26 | _filterDynamicData = 27 | new Button 28 | { 29 | Text = "Filter" 30 | }; 31 | 32 | _sortDynamicData = 33 | new Button 34 | { 35 | Text = "Sort" 36 | }; 37 | 38 | _sourceCacheDynamicData = 39 | new Button 40 | { 41 | Text = "Source Cache" 42 | }; 43 | 44 | _mainLayout = 45 | new StackLayout 46 | { 47 | Children = 48 | { 49 | _simpleDynamicData, 50 | _filterDynamicData, 51 | _sortDynamicData, 52 | _sourceCacheDynamicData, 53 | } 54 | }; 55 | 56 | _mainScroll = 57 | new ScrollView 58 | { 59 | Content = _mainLayout, 60 | }; 61 | 62 | Content = _mainScroll; 63 | } 64 | 65 | protected override void SetupReactiveExtensions () 66 | { 67 | _simpleDynamicData 68 | .Events() 69 | .Clicked 70 | .NavigateTo( 71 | this, 72 | _ => new SimpleDynamicData()) 73 | .DisposeWith(SubscriptionDisposables); 74 | 75 | _filterDynamicData 76 | .Events() 77 | .Clicked 78 | .NavigateTo( 79 | this, 80 | _ => new FilterDynamicData()) 81 | .DisposeWith(SubscriptionDisposables); 82 | 83 | _sortDynamicData 84 | .Events() 85 | .Clicked 86 | .NavigateTo( 87 | this, 88 | _ => new SortDynamicData()) 89 | .DisposeWith(SubscriptionDisposables); 90 | 91 | _sourceCacheDynamicData 92 | .Events() 93 | .Clicked 94 | .NavigateTo( 95 | this, 96 | _ => new SourceCacheDynamicData()) 97 | .DisposeWith(SubscriptionDisposables); 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/DynamicData/FilterDynamicData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using ReactiveUI; 6 | using ReactiveUI.XamForms; 7 | using Xamarin.Forms; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace ReactiveExtensionExamples.UserInterface.Pages.DynamicData 11 | { 12 | public class FilterDynamicData : ReactiveContentPage 13 | { 14 | Entry textEntry; 15 | CollectionView searchResults; 16 | Button search; 17 | ActivityIndicator _loading; 18 | SerialDisposable _searchErrorDisposable; 19 | 20 | public FilterDynamicData () { 21 | Title = "Rx - Filter Dynamic Data"; 22 | 23 | this.ViewModel = new ViewModels.DynamicData.FilterDynamicDataViewModel(); 24 | 25 | Content = new StackLayout 26 | { 27 | Padding = new Thickness(8d), 28 | Children = { 29 | (textEntry = new Entry{ Placeholder = "Enter Search Terms" }), 30 | (search = new Button{ Text = "Search" }), 31 | (_loading = new ActivityIndicator{}), 32 | (searchResults = new CollectionView() { 33 | VerticalOptions = LayoutOptions.FillAndExpand, 34 | HorizontalOptions = LayoutOptions.FillAndExpand, 35 | ItemTemplate = new DataTemplate(typeof(Cells.RssEntryCell)), 36 | ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem, 37 | }) 38 | } 39 | }; 40 | 41 | this.WhenActivated((CompositeDisposable disposables) => 42 | { 43 | this.Bind(ViewModel, x => x.SearchQuery, c => c.textEntry.Text) 44 | .DisposeWith(disposables); 45 | 46 | this.BindCommand(ViewModel, x => x.Search, c => c.search, this.WhenAnyValue(x => x.ViewModel.SearchQuery)) 47 | .DisposeWith(disposables); 48 | 49 | Observable 50 | .CombineLatest( 51 | this.WhenAnyValue(x => x.ViewModel.SearchQuery) 52 | .Select(x => !string.IsNullOrEmpty(x)) 53 | .DistinctUntilChanged(), 54 | this.WhenAnyValue(x => x.ViewModel.ResultCount), 55 | (hasSearch, resultCount) => hasSearch ? $"{resultCount} Matches" : "Search") 56 | .ObserveOn(RxApp.MainThreadScheduler) 57 | .BindTo(this, x => x.search.Text) 58 | .DisposeWith(disposables); 59 | 60 | //This is a one-way bind 61 | this.OneWayBind(ViewModel, x => x.SearchResults, c => c.searchResults.ItemsSource) 62 | .DisposeWith(disposables); 63 | 64 | this.WhenAnyValue(x => x.ViewModel) 65 | .Where(x => x != null) 66 | .SelectUnit() 67 | .InvokeCommand(this, x => x.ViewModel.Search) 68 | .DisposeWith(disposables); 69 | }); 70 | } 71 | } 72 | } 73 | 74 | 75 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/DynamicData/SimpleDynamicData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using ReactiveUI; 6 | using ReactiveUI.XamForms; 7 | using Xamarin.Forms; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace ReactiveExtensionExamples.UserInterface.Pages.DynamicData 11 | { 12 | public class SimpleDynamicData : ReactiveContentPage 13 | { 14 | CollectionView searchResults; 15 | Button search; 16 | ActivityIndicator _loading; 17 | SerialDisposable _searchErrorDisposable; 18 | 19 | public SimpleDynamicData () { 20 | Title = "Rx - Simple Dynamic Data"; 21 | 22 | this.ViewModel = new ViewModels.DynamicData.SimpleDynamicDataViewModel(); 23 | 24 | Content = new StackLayout 25 | { 26 | Padding = new Thickness(8d), 27 | Children = { 28 | (search = new Button{ Text = "Search" }), 29 | (_loading = new ActivityIndicator{}), 30 | (searchResults = new CollectionView() { 31 | VerticalOptions = LayoutOptions.FillAndExpand, 32 | HorizontalOptions = LayoutOptions.FillAndExpand, 33 | ItemTemplate = new DataTemplate(typeof(Cells.RssEntryCell)), 34 | ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem, 35 | }) 36 | } 37 | }; 38 | 39 | this.WhenActivated((CompositeDisposable disposables) => 40 | { 41 | 42 | //This is a one-way bind 43 | this.OneWayBind(ViewModel, x => x.SearchResults, c => c.searchResults.ItemsSource) 44 | .DisposeWith(disposables); 45 | 46 | this.BindCommand(ViewModel, x => x.Search, c => c.search)//this.WhenAnyValue(x => x.ViewModel.SearchQuery) 47 | .DisposeWith(disposables); 48 | 49 | this.WhenAnyValue(x => x.ViewModel) 50 | .Where(x => x != null) 51 | .SelectUnit() 52 | .InvokeCommand(this, x => x.ViewModel.Search) 53 | .DisposeWith(disposables); 54 | }); 55 | } 56 | } 57 | } 58 | 59 | 60 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/DynamicData/SortDynamicData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using ReactiveUI; 6 | using ReactiveUI.XamForms; 7 | using Xamarin.Forms; 8 | using System.Runtime.InteropServices; 9 | using ReactiveExtensionExamples.ViewModels.DynamicData; 10 | 11 | namespace ReactiveExtensionExamples.UserInterface.Pages.DynamicData 12 | { 13 | public class SortDynamicData : ReactiveContentPage 14 | { 15 | Button sortUpdatedAscending, sortUpdatedDescending, sortTitleAscending, sortTitleDescending; 16 | CollectionView searchResults; 17 | ActivityIndicator _loading; 18 | SerialDisposable _searchErrorDisposable; 19 | 20 | public SortDynamicData () { 21 | Title = "Rx - Sort Dynamic Data"; 22 | 23 | this.ViewModel = new ViewModels.DynamicData.SortDynamicDataViewModel(); 24 | 25 | Content = new StackLayout 26 | { 27 | Padding = new Thickness(8d), 28 | Children = { 29 | (sortTitleAscending = new Button{ Text = "Title - Ascending" }), 30 | (sortTitleDescending = new Button{ Text = "Title - Descending" }), 31 | (sortUpdatedAscending = new Button{ Text = "Updated - Ascending" }), 32 | (sortUpdatedDescending = new Button{ Text = "Updated - Descending" }), 33 | (_loading = new ActivityIndicator{}), 34 | (searchResults = new CollectionView() { 35 | VerticalOptions = LayoutOptions.FillAndExpand, 36 | HorizontalOptions = LayoutOptions.FillAndExpand, 37 | ItemTemplate = new DataTemplate(typeof(Cells.RssEntryCell)), 38 | ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem, 39 | }) 40 | } 41 | }; 42 | 43 | this.WhenActivated((CompositeDisposable disposables) => 44 | { 45 | Observable 46 | .Merge( 47 | sortTitleAscending.Events().Clicked.Select(_ => SortType.TitleAscending), 48 | sortTitleDescending.Events().Clicked.Select(_ => SortType.TitleDescending), 49 | sortUpdatedAscending.Events().Clicked.Select(_ => SortType.DateTimeAscending), 50 | sortUpdatedDescending.Events().Clicked.Select(_ => SortType.DateTimeDescending)) 51 | .Do(x => this.ViewModel.SelectedSortType = x) 52 | .Subscribe() 53 | .DisposeWith(disposables); 54 | 55 | //This is a one-way bind 56 | this.OneWayBind(ViewModel, x => x.SearchResults, c => c.searchResults.ItemsSource) 57 | .DisposeWith(disposables); 58 | 59 | this.WhenAnyValue(x => x.ViewModel) 60 | .Where(x => x != null) 61 | .SelectUnit() 62 | .InvokeCommand(this, x => x.ViewModel.Search) 63 | .DisposeWith(disposables); 64 | }); 65 | } 66 | } 67 | } 68 | 69 | 70 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/DynamicData/SourceCacheDynamicData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using ReactiveUI; 6 | using ReactiveUI.XamForms; 7 | using Xamarin.Forms; 8 | using System.Runtime.InteropServices; 9 | 10 | namespace ReactiveExtensionExamples.UserInterface.Pages.DynamicData 11 | { 12 | public class SourceCacheDynamicData : ReactiveContentPage 13 | { 14 | CollectionView searchResults; 15 | ActivityIndicator _loading; 16 | SerialDisposable _searchErrorDisposable; 17 | 18 | public SourceCacheDynamicData () { 19 | Title = "Rx - Source Cache Dynamic Data"; 20 | 21 | this.ViewModel = new ViewModels.DynamicData.SourceCacheDynamicDataViewModel(); 22 | 23 | Content = new StackLayout 24 | { 25 | Padding = new Thickness(8d), 26 | Children = { 27 | (_loading = new ActivityIndicator{}), 28 | (searchResults = new CollectionView() { 29 | VerticalOptions = LayoutOptions.FillAndExpand, 30 | HorizontalOptions = LayoutOptions.FillAndExpand, 31 | ItemTemplate = new DataTemplate(typeof(Cells.RssEntryCell)), 32 | ItemSizingStrategy = ItemSizingStrategy.MeasureFirstItem, 33 | ItemsUpdatingScrollMode = ItemsUpdatingScrollMode.KeepScrollOffset, 34 | }) 35 | } 36 | }; 37 | 38 | this.WhenActivated((CompositeDisposable disposables) => 39 | { 40 | //This is a one-way bind 41 | this.OneWayBind(ViewModel, x => x.SearchResults, c => c.searchResults.ItemsSource) 42 | .DisposeWith(disposables); 43 | 44 | this.WhenAnyValue(x => x.ViewModel) 45 | .Where(x => x != null) 46 | .SelectUnit() 47 | .InvokeCommand(this, x => x.ViewModel.Search) 48 | .DisposeWith(disposables); 49 | }); 50 | } 51 | } 52 | } 53 | 54 | 55 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/Merge.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | using System.Reactive.Linq; 4 | using System.Reactive; 5 | using System.Reactive.Disposables; 6 | 7 | namespace ReactiveExtensionExamples.UserInterface.Pages 8 | { 9 | public class Merge : PageBase 10 | { 11 | Label outputLabel; 12 | Button button1, button2, button3, button4; 13 | 14 | protected override void SetupUserInterface () 15 | { 16 | Title = "Rx - Merge"; 17 | 18 | button1 = new Button{ Text = "Peter Venkman" }; 19 | 20 | button2 = new Button{ Text = "Ray Stantz" }; 21 | 22 | button3 = new Button{ Text = "Egon Spengler" }; 23 | 24 | button4 = new Button{ Text = "Winston Zeddemore" }; 25 | 26 | Content = new StackLayout { 27 | Padding = new Thickness(8d), 28 | Spacing = 16d, 29 | Children = { 30 | button1, 31 | button2, 32 | button3, 33 | button4, 34 | (outputLabel = 35 | new Label { 36 | HorizontalTextAlignment = TextAlignment.Center, 37 | Text = "Whoa, It's Slimer! Blast 'em!" 38 | } 39 | ), 40 | 41 | } 42 | }; 43 | 44 | 45 | } 46 | 47 | protected override void SetupReactiveExtensions () 48 | { 49 | Observable 50 | .Merge( 51 | Observable 52 | .FromEventPattern ( 53 | x => button1.Clicked += x, 54 | x => button1.Clicked -= x), 55 | Observable 56 | .FromEventPattern ( 57 | x => button2.Clicked += x, 58 | x => button2.Clicked -= x), 59 | Observable 60 | .FromEventPattern ( 61 | x => button3.Clicked += x, 62 | x => button3.Clicked -= x), 63 | Observable 64 | .FromEventPattern ( 65 | x => button4.Clicked += x, 66 | x => button4.Clicked -= x)) 67 | .Select (args => 68 | string.Format ("Who merged the Streams?{0}{1}", 69 | Environment.NewLine, ((Button)args.Sender).Text)) 70 | .Subscribe (text => Device.BeginInvokeOnMainThread(() => outputLabel.Text = text)) 71 | .DisposeWith(SubscriptionDisposables); 72 | 73 | } 74 | } 75 | } 76 | 77 | 78 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/NavigationContainerPage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | using System.Reactive.Subjects; 4 | using System.Reactive.Linq; 5 | using ReactiveUI; 6 | 7 | namespace ReactiveExtensionExamples.UserInterface.Pages 8 | { 9 | public class NavigationContainerPage : MasterDetailPage 10 | { 11 | readonly NavListPage navListPage = new NavListPage(); 12 | 13 | public NavigationContainerPage () 14 | { 15 | var primaryNavPage = new NavigationPage(navListPage){ 16 | Title = "Reactive Examples", 17 | }; 18 | 19 | Master = primaryNavPage; 20 | 21 | this.MasterBehavior = MasterBehavior.Popover; 22 | this.IsPresented = true; 23 | 24 | navListPage 25 | .SelectedNavigation 26 | .ObserveOn (RxApp.MainThreadScheduler) 27 | .StartWith(NavigationPages.Delay) 28 | .Subscribe (navPage => { 29 | 30 | Page selectedPage = null; 31 | switch (navPage) { 32 | case NavigationPages.Async: 33 | selectedPage = new Pages.Async(); 34 | break; 35 | case NavigationPages.AsyncEvents: 36 | selectedPage = new Pages.AsyncEvent(); 37 | break; 38 | case NavigationPages.Delay: 39 | selectedPage = new Pages.Delay(); 40 | break; 41 | case NavigationPages.Throttle: 42 | selectedPage = new Pages.Throttle(); 43 | break; 44 | case NavigationPages.Buffer: 45 | selectedPage = new Pages.Buffer(); 46 | break; 47 | case NavigationPages.BufferWithWhere: 48 | selectedPage = new Pages.BufferWithWhere(); 49 | break; 50 | case NavigationPages.Merge: 51 | selectedPage = new Pages.Merge(); 52 | break; 53 | case NavigationPages.Sample: 54 | selectedPage = new Pages.Sample(); 55 | break; 56 | case NavigationPages.Scan: 57 | selectedPage = new Pages.Scan(); 58 | break; 59 | case NavigationPages.AsyncToObservable: 60 | selectedPage = new Pages.AsyncToObservable (); 61 | break; 62 | case NavigationPages.CombineLatest: 63 | selectedPage = new Pages.CombineLatest (); 64 | break; 65 | case NavigationPages.StandardSearch: 66 | selectedPage = new Pages.StandardSearch(); 67 | break; 68 | case NavigationPages.DynamicDataDashboard: 69 | selectedPage = new Pages.DynamicData.DynamicDataDashboard(); 70 | break; 71 | case NavigationPages.SearchWithReactiveExtensions: 72 | selectedPage = new Pages.SearchWithReactiveExtensions(); 73 | break; 74 | case NavigationPages.RxUiSearch: 75 | selectedPage = new Pages.ReactiveUiSearch(); 76 | break; 77 | case NavigationPages.RxUiColorSlider: 78 | selectedPage = new Pages.ReactiveUiColorSlider(); 79 | break; 80 | case NavigationPages.RxUiLogin: 81 | selectedPage = new Pages.ReactiveUiLogin(); 82 | break; 83 | case NavigationPages.RxUiXamarinEssentials: 84 | selectedPage = new Pages.ReactiveUiEssentials(); 85 | break; 86 | default: 87 | break; 88 | } 89 | 90 | if(selectedPage != null) { 91 | var detailNavPage = new NavigationPage(selectedPage) {}; 92 | 93 | Detail = detailNavPage; 94 | } 95 | 96 | this.IsPresented = false; 97 | 98 | }); 99 | } 100 | 101 | enum NavigationPages { 102 | Async, 103 | AsyncEvents, 104 | Delay, 105 | Throttle, 106 | Buffer, 107 | BufferWithWhere, 108 | Merge, 109 | Sample, 110 | Scan, 111 | AsyncToObservable, 112 | CombineLatest, 113 | RxUiColorSlider, 114 | RxUiLogin, 115 | StandardSearch, 116 | SearchWithReactiveExtensions, 117 | RxUiSearch, 118 | RxUiXamarinEssentials, 119 | DynamicDataDashboard, 120 | } 121 | 122 | class NavListPage : ContentPage { 123 | readonly Subject selectedNavigation = new Subject(); 124 | public IObservable SelectedNavigation { get { return selectedNavigation.AsObservable(); } } 125 | 126 | public NavListPage () { 127 | Title = "Reactive Examples"; 128 | 129 | var scrollContainer = new ScrollView { 130 | BackgroundColor = Color.FromHex("#dddddd"), 131 | }; 132 | 133 | var navigationContainer = new StackLayout { 134 | Spacing = 8d 135 | }; 136 | 137 | scrollContainer.Content = navigationContainer; 138 | 139 | var delay = new Button { 140 | Text = "Delay", 141 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.Delay)), 142 | }; 143 | navigationContainer.Children.Add(delay); 144 | 145 | var throttle = new Button { 146 | Text = "Throttle", 147 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.Throttle)), 148 | }; 149 | navigationContainer.Children.Add(throttle); 150 | 151 | var buffer = new Button { 152 | Text = "Buffer", 153 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.Buffer)), 154 | }; 155 | navigationContainer.Children.Add(buffer); 156 | 157 | var bufferWithWhere = new Button { 158 | Text = "Buffer with Where", 159 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.BufferWithWhere)), 160 | }; 161 | navigationContainer.Children.Add(bufferWithWhere); 162 | 163 | var sample = new Button { 164 | Text = "Sample", 165 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.Sample)), 166 | }; 167 | navigationContainer.Children.Add(sample); 168 | 169 | var scan = new Button { 170 | Text = "Scan", 171 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.Scan)), 172 | }; 173 | navigationContainer.Children.Add(scan); 174 | 175 | var merge = new Button { 176 | Text = "Merge", 177 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.Merge)), 178 | }; 179 | navigationContainer.Children.Add(merge); 180 | 181 | var combineLatest = new Button { 182 | Text = "Combine Latest", 183 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.CombineLatest)), 184 | }; 185 | navigationContainer.Children.Add(combineLatest); 186 | 187 | var asyncToObservable = new Button { 188 | Text = "Mix Async With Observables", 189 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.AsyncToObservable)), 190 | }; 191 | navigationContainer.Children.Add(asyncToObservable); 192 | 193 | var reactiveAsync = new Button { 194 | Text = "Async", 195 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.Async)), 196 | }; 197 | navigationContainer.Children.Add(reactiveAsync); 198 | 199 | var asyncEvents = new Button { 200 | Text = "Async Events", 201 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.AsyncEvents)), 202 | }; 203 | navigationContainer.Children.Add(asyncEvents); 204 | 205 | var standardSearch = new Button 206 | { 207 | Text = "Standard Search", 208 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.StandardSearch)), 209 | }; 210 | navigationContainer.Children.Add(standardSearch); 211 | 212 | var dynamicDataDashboard = new Button 213 | { 214 | Text = "Dynamic Data", 215 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.DynamicDataDashboard)), 216 | }; 217 | navigationContainer.Children.Add(dynamicDataDashboard); 218 | 219 | var searchWithReactiveExtensions = new Button 220 | { 221 | Text = "Search with Reactive Extensions", 222 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.SearchWithReactiveExtensions)), 223 | }; 224 | navigationContainer.Children.Add(searchWithReactiveExtensions); 225 | 226 | var rxuiSearch = new Button 227 | { 228 | Text = "RxUI - Search", 229 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.RxUiSearch)), 230 | }; 231 | navigationContainer.Children.Add(rxuiSearch); 232 | 233 | var rxuiColorSlider = new Button { 234 | Text = "RxUI - Color Slider", 235 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.RxUiColorSlider)), 236 | }; 237 | navigationContainer.Children.Add(rxuiColorSlider); 238 | 239 | var rxuiLogin = new Button { 240 | Text = "RxUI - Login", 241 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.RxUiLogin)), 242 | }; 243 | navigationContainer.Children.Add(rxuiLogin); 244 | 245 | var rxuiEssentials = new Button { 246 | Text = "RxUI - Xamarin Essentials", 247 | Command = new Command(() => selectedNavigation.OnNext(NavigationPages.RxUiXamarinEssentials)), 248 | }; 249 | navigationContainer.Children.Add(rxuiEssentials); 250 | 251 | var reactiveLogo = new Image { 252 | Source = "reactive_logo.png", 253 | Aspect = Aspect.AspectFit, 254 | Margin = new Thickness(8d) 255 | }; 256 | navigationContainer.Children.Add(reactiveLogo); 257 | 258 | Content = scrollContainer; 259 | } 260 | } 261 | } 262 | } 263 | 264 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/NonReactivePage.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | 4 | namespace ReactiveExtensionExamples.UserInterface.Pages 5 | { 6 | public class NonReactivePage : ContentPage 7 | { 8 | int clickCount = 0; 9 | 10 | Label buttonClickedInformation; 11 | Button myButton; 12 | 13 | public NonReactivePage () 14 | { 15 | 16 | myButton = new Button { 17 | Text = "This is a button" 18 | }; 19 | 20 | myButton.Clicked += MyButton_Clicked; 21 | buttonClickedInformation = new Label { }; 22 | 23 | Content = new StackLayout { 24 | Children = { 25 | myButton, 26 | buttonClickedInformation 27 | } 28 | }; 29 | } 30 | 31 | void MyButton_Clicked (object sender, EventArgs e) 32 | { 33 | clickCount++; 34 | buttonClickedInformation.Text = string.Format ("Clicked {0} times", clickCount); 35 | } 36 | } 37 | } 38 | 39 | 40 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/PageBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | using System.Reactive.Disposables; 4 | 5 | namespace ReactiveExtensionExamples.UserInterface.Pages 6 | { 7 | public class PageBase : ContentPage 8 | { 9 | protected readonly CompositeDisposable SubscriptionDisposables = new CompositeDisposable (); 10 | 11 | public PageBase () 12 | { 13 | SetupUserInterface (); 14 | } 15 | 16 | protected virtual void SetupUserInterface () {} 17 | 18 | protected virtual void SetupReactiveExtensions () {} 19 | 20 | protected override void OnAppearing () 21 | { 22 | SetupReactiveExtensions (); 23 | 24 | base.OnAppearing (); 25 | } 26 | 27 | protected override void OnDisappearing () 28 | { 29 | SubscriptionDisposables.Clear (); 30 | 31 | base.OnDisappearing (); 32 | } 33 | } 34 | } 35 | 36 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/ReactiveUiColorSlider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Disposables; 3 | using System.Reactive.Linq; 4 | using ReactiveUI; 5 | using Xamarin.Forms; 6 | using System.Threading.Tasks; 7 | using ReactiveUI.XamForms; 8 | 9 | namespace ReactiveExtensionExamples.UserInterface.Pages 10 | { 11 | public class ReactiveUiColorSlider: ReactiveContentPage 12 | { 13 | readonly CompositeDisposable subscriptionDisposables = new CompositeDisposable (); 14 | 15 | BoxView colorDisplay; 16 | 17 | Slider red, green, blue; 18 | 19 | public ReactiveUiColorSlider () 20 | { 21 | ViewModel = new ReactiveExtensionExamples.ViewModels.ColorSlider (); 22 | 23 | Title = "RxUI - Color Slider"; 24 | 25 | Content = new StackLayout { 26 | Padding = new Thickness(40d), 27 | Children = { 28 | (colorDisplay = new BoxView{ HeightRequest = 250 }), 29 | 30 | new Label{ Text = "Red"}, 31 | (red = new Slider(0, 255, 0)), 32 | 33 | new Label{ Text = "Green"}, 34 | (green = new Slider(0, 255, 0)), 35 | 36 | new Label{ Text = "Blue"}, 37 | (blue = new Slider(0, 255, 0)) 38 | } 39 | }; 40 | 41 | this.WhenActivated((CompositeDisposable disposables) => 42 | { 43 | this.Bind (ViewModel, vm => vm.Red, c => c.red.Value) 44 | .DisposeWith(disposables); 45 | 46 | this.Bind (ViewModel, vm => vm.Green, c => c.green.Value) 47 | .DisposeWith(disposables); 48 | 49 | this.Bind (ViewModel, vm => vm.Blue, c => c.blue.Value) 50 | .DisposeWith(disposables); 51 | 52 | this.WhenAnyValue (x => x.ViewModel.Color) 53 | .Select (color => Color.FromRgba (color.R, color.G, color.B, color.A)) 54 | .ObserveOn (RxApp.MainThreadScheduler) 55 | .BindTo (this, x => x.colorDisplay.BackgroundColor) 56 | .DisposeWith(disposables); 57 | }); 58 | } 59 | } 60 | } 61 | 62 | 63 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/ReactiveUiEssentials.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Disposables; 3 | using System.Reactive.Linq; 4 | using ReactiveUI; 5 | using Xamarin.Forms; 6 | using System.Threading.Tasks; 7 | using ReactiveUI.XamForms; 8 | 9 | namespace ReactiveExtensionExamples.UserInterface.Pages 10 | { 11 | public class ReactiveUiEssentials : ReactiveContentPage 12 | { 13 | BoxView colorDisplay; 14 | 15 | Slider x, y, z; 16 | 17 | public ReactiveUiEssentials () 18 | { 19 | ViewModel = new ReactiveExtensionExamples.ViewModels.XamarinEssentials (); 20 | 21 | Title = "RxUI - Xamarin Essentials"; 22 | 23 | Content = new StackLayout { 24 | Padding = new Thickness(40d), 25 | Children = { 26 | (colorDisplay = new BoxView{ HeightRequest = 250 }), 27 | 28 | new Label{ Text = "X"}, 29 | (x = new Slider(-1, 1, 0){ InputTransparent = true }), 30 | 31 | new Label{ Text = "Y"}, 32 | (y = new Slider(-1, 1, 0){ InputTransparent = true }), 33 | 34 | new Label{ Text = "Z"}, 35 | (z = new Slider(-1, 1, 0){ InputTransparent = true }) 36 | } 37 | }; 38 | 39 | this.WhenActivated((CompositeDisposable disposables) => 40 | { 41 | this.OneWayBind (ViewModel, vm => vm.X, c => c.x.Value) 42 | .DisposeWith(disposables); 43 | 44 | this.OneWayBind (ViewModel, vm => vm.Y, c => c.y.Value) 45 | .DisposeWith(disposables); 46 | 47 | this.OneWayBind (ViewModel, vm => vm.Z, c => c.z.Value) 48 | .DisposeWith(disposables); 49 | 50 | this.WhenAnyValue (x => x.ViewModel.Color) 51 | .ObserveOn (RxApp.MainThreadScheduler) 52 | .Select (color => Color.FromRgba (color.R, color.G, color.B, color.A)) 53 | .BindTo (this, x => x.colorDisplay.BackgroundColor) 54 | .DisposeWith(disposables); 55 | }); 56 | } 57 | } 58 | } 59 | 60 | 61 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/ReactiveUiLogin.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using ReactiveUI; 6 | using ReactiveUI.XamForms; 7 | using Xamarin.Forms; 8 | 9 | namespace ReactiveExtensionExamples.UserInterface.Pages 10 | { 11 | public class ReactiveUiLogin : ReactiveContentPage 12 | { 13 | Entry emailEntry, passwordEntry; 14 | 15 | Button login; 16 | 17 | ActivityIndicator loading; 18 | 19 | public ReactiveUiLogin () 20 | { 21 | ViewModel = new ReactiveExtensionExamples.ViewModels.Login (); 22 | 23 | Title = "RxUI - Login"; 24 | 25 | Content = new StackLayout { 26 | Padding = new Thickness(40d), 27 | Children = { 28 | (emailEntry = new Entry{ Placeholder = "Email" }), 29 | (passwordEntry = new Entry { Placeholder = "Password", IsPassword = true }), 30 | (login = new Button{ Text = "Login" }), 31 | (loading = new ActivityIndicator{ HorizontalOptions = LayoutOptions.Center }), 32 | } 33 | }; 34 | 35 | this.WhenActivated((CompositeDisposable disposables) => 36 | { 37 | this.Bind (ViewModel, vm => vm.EmailAddress, c => c.emailEntry.Text) 38 | .DisposeWith(disposables); 39 | 40 | this.Bind (ViewModel, vm => vm.Password, c => c.passwordEntry.Text) 41 | .DisposeWith(disposables); 42 | 43 | this.OneWayBind (ViewModel, vm => vm.IsLoading, c => c.loading.IsRunning) 44 | .DisposeWith(disposables); 45 | 46 | this.OneWayBind (ViewModel, vm => vm.IsLoading, c => c.loading.IsVisible) 47 | .DisposeWith(disposables); 48 | 49 | this.BindCommand (ViewModel, vm => vm.PerformLogin, c => c.login) 50 | .DisposeWith(disposables); 51 | 52 | this.WhenAnyObservable (x => x.ViewModel.PerformLogin) 53 | .ObserveOn (RxApp.MainThreadScheduler) 54 | .SelectMany (async _ => 55 | { 56 | await DisplayAlert("Log In", "It's Log, It's Log", "It's Big, It's Heavy, It's Wood"); 57 | return Unit.Default; 58 | }) 59 | .Subscribe() 60 | .DisposeWith(disposables); 61 | }); 62 | } 63 | } 64 | } 65 | 66 | 67 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/ReactiveUiSearch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using System.Reactive.Threading.Tasks; 6 | using ReactiveUI; 7 | using ReactiveUI.XamForms; 8 | using Xamarin.Forms; 9 | 10 | namespace ReactiveExtensionExamples.UserInterface.Pages 11 | { 12 | public class ReactiveUiSearch : ReactiveContentPage 13 | { 14 | private Entry _textEntry; 15 | private ListView _searchResults; 16 | private RefreshView _pullToRefresh; 17 | private Button _search; 18 | private ActivityIndicator _loading; 19 | private SerialDisposable _searchErrorDisposable; 20 | 21 | public ReactiveUiSearch() { 22 | Title = "Rx - Search"; 23 | 24 | this.ViewModel = new ViewModels.SearchViewModel(); 25 | 26 | Content = new StackLayout 27 | { 28 | Padding = new Thickness(8d), 29 | Children = 30 | { 31 | (_textEntry = new Entry{ Placeholder = "Enter Search Terms" }), 32 | (_search = new Button{ Text = "Search" }), 33 | (_loading = new ActivityIndicator{}), 34 | (_pullToRefresh = 35 | new RefreshView 36 | { 37 | Content = 38 | _searchResults = 39 | new ListView(ListViewCachingStrategy.RecycleElement) 40 | { 41 | ItemTemplate = new DataTemplate(typeof(DuckDuckGoResultCell)) 42 | }, 43 | HorizontalOptions = LayoutOptions.FillAndExpand, 44 | VerticalOptions = LayoutOptions.FillAndExpand, 45 | }), 46 | } 47 | }; 48 | 49 | this.WhenActivated( 50 | (CompositeDisposable disposables) => 51 | { 52 | _searchErrorDisposable?.Dispose(); 53 | _searchErrorDisposable = new SerialDisposable(); 54 | 55 | //TODO: RxSUI - Item 1 - Here we are just setting up bindings to our UI Elements 56 | //This is a two-way bind 57 | this.Bind(ViewModel, x => x.SearchQuery, c => c._textEntry.Text) 58 | .DisposeWith(disposables); 59 | 60 | this.BindCommand(ViewModel, x => x.Search, c => c._search, this.WhenAnyValue(x => x.ViewModel.SearchQuery)) 61 | .DisposeWith(disposables); 62 | 63 | //Once this event is fired off, it will start the refresh 64 | Observable 65 | .FromEventPattern( 66 | x => _pullToRefresh.Refreshing += x, 67 | x => _pullToRefresh.Refreshing -= x) 68 | .Select(_ => this.WhenAnyValue(x => x.ViewModel.SearchQuery).Take(1)) 69 | .Switch() 70 | .InvokeCommand(this, x => x.ViewModel.Search) 71 | .DisposeWith(disposables); 72 | 73 | //This will only trigger when the search command completes 74 | this.WhenAnyObservable(x => x.ViewModel.Search) 75 | .Select(_ => false) 76 | .ObserveOn(RxApp.MainThreadScheduler) 77 | .BindTo(this, ui => ui._pullToRefresh.IsRefreshing) 78 | .DisposeWith(disposables); 79 | 80 | //This is a one-way bind 81 | this.OneWayBind(ViewModel, x => x.SearchResults, c => c._searchResults.ItemsSource) 82 | .DisposeWith(disposables); 83 | 84 | //TODO: RxSUI - Item 2 - User error allows us to interact with our users and get feedback on how to handle an exception 85 | this.WhenAnyValue(x => x.ViewModel.SearchError) 86 | .Where(x => x != null) 87 | .Subscribe(searchError => 88 | { 89 | _searchErrorDisposable.Disposable = 90 | searchError 91 | .RegisterHandler(async interaction => 92 | { 93 | var result = await this.DisplayAlert("Error", $"{interaction.Input}{Environment.NewLine}Would you like to retry?", "Retry", "Cancel"); 94 | interaction.SetOutput(result); 95 | }); 96 | }) 97 | .DisposeWith(disposables); 98 | 99 | _searchErrorDisposable 100 | .DisposeWith(disposables); 101 | }); 102 | } 103 | 104 | class DuckDuckGoResultCell : ReactiveViewCell 105 | { 106 | Image icon; 107 | 108 | Label displayText; 109 | 110 | public DuckDuckGoResultCell() 111 | { 112 | var grid = new Grid 113 | { 114 | Padding = new Thickness(8d), 115 | ColumnDefinitions = { 116 | new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, 117 | new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) } 118 | } 119 | }; 120 | 121 | icon = new Image 122 | { 123 | VerticalOptions = LayoutOptions.FillAndExpand, 124 | Aspect = Aspect.AspectFit 125 | }; 126 | grid.Children.Add(icon, 0, 0); 127 | 128 | displayText = new Label 129 | { 130 | HorizontalOptions = LayoutOptions.FillAndExpand, 131 | VerticalOptions = LayoutOptions.FillAndExpand 132 | }; 133 | grid.Children.Add(displayText, 1, 0); 134 | 135 | View = grid; 136 | 137 | this.WhenActivated((CompositeDisposable disposables) => 138 | { 139 | this.WhenAnyValue(x => x.ViewModel.ImageUrl) 140 | .ObserveOn(RxApp.MainThreadScheduler) 141 | .Subscribe(x => icon.Source = x) 142 | .DisposeWith(disposables); 143 | 144 | this.OneWayBind(ViewModel, x => x.DisplayText, x => x.displayText.Text) 145 | .DisposeWith(disposables); 146 | }); 147 | } 148 | } 149 | } 150 | } 151 | 152 | 153 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/Sample.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Concurrency; 4 | using System.Reactive.Disposables; 5 | using System.Reactive.Linq; 6 | using Xamarin.Forms; 7 | 8 | namespace ReactiveExtensionExamples.UserInterface.Pages 9 | { 10 | public class Sample : PageBase 11 | { 12 | Entry textEntry; 13 | WebView webView; 14 | 15 | protected override void SetupUserInterface () 16 | { 17 | Title = "Rx - Sample"; 18 | 19 | Content = new StackLayout { 20 | Padding = new Thickness(8d), 21 | Children = { 22 | (textEntry = new Entry{ Placeholder = "Enter Search Terms" }), 23 | (webView = new WebView { 24 | VerticalOptions = LayoutOptions.FillAndExpand, 25 | HorizontalOptions = LayoutOptions.FillAndExpand, 26 | }) 27 | } 28 | }; 29 | } 30 | 31 | protected override void SetupReactiveExtensions () 32 | { 33 | Observable 34 | .FromEventPattern, TextChangedEventArgs> ( 35 | x => textEntry.TextChanged += x, 36 | x => textEntry.TextChanged -= x) 37 | .Select(args => 38 | string.Format("https://frinkiac.com/?q={0}", args.EventArgs.NewTextValue.Replace(" ", "+"))) 39 | .Subscribe ( 40 | searchUrl => Device.BeginInvokeOnMainThread(() => webView.Source = searchUrl), 41 | ex => Device.BeginInvokeOnMainThread(() => webView.Source = "https://frinkiac.com/caption/S04E05/1135500")) 42 | .DisposeWith(SubscriptionDisposables); 43 | } 44 | } 45 | } 46 | 47 | 48 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/Scan.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net.Http; 5 | using System.Reactive.Disposables; 6 | using System.Reactive.Linq; 7 | using System.Threading.Tasks; 8 | using System.Xml.Linq; 9 | using Xamarin.Forms; 10 | using System.Threading; 11 | using ReactiveExtensionExamples.Models; 12 | using ReactiveExtensionExamples.Services.Api; 13 | 14 | namespace ReactiveExtensionExamples.UserInterface.Pages 15 | { 16 | public class Scan : PageBase 17 | { 18 | CollectionView rssFeed; 19 | 20 | readonly HttpClient client = new HttpClient(); 21 | 22 | protected override void SetupUserInterface () 23 | { 24 | Title = "Rx - Scan"; 25 | 26 | 27 | Content = new StackLayout { 28 | Children = { 29 | (rssFeed = new CollectionView { 30 | ItemTemplate = new DataTemplate (typeof(Cells.RssEntryCell)), 31 | ItemSizingStrategy = ItemSizingStrategy.MeasureAllItems, 32 | }) 33 | } 34 | }; 35 | } 36 | 37 | protected override void SetupReactiveExtensions () 38 | { 39 | Observable 40 | .Interval(TimeSpan.FromMilliseconds(300)) 41 | .StartWith(0L) 42 | .Select(_ => Observable.FromAsync(cancellationToken => DownloadMultipleRss(cancellationToken))) 43 | .Switch() 44 | .Scan(new List(), 45 | (accumulatedItems, newItems) => 46 | { 47 | var itemsToAdd = 48 | newItems 49 | .Where(x => !accumulatedItems.Any(ai => ai.Id.Equals(x.Id))) 50 | .Select(x => { x.New = true; return x;}) 51 | .ToList(); 52 | 53 | foreach (var item in accumulatedItems) 54 | item.New = false; 55 | 56 | accumulatedItems.InsertRange(0, itemsToAdd); 57 | 58 | return 59 | accumulatedItems 60 | .OrderByDescending(x => x.New) 61 | .ThenByDescending(x => x.Updated) 62 | .Take(250) 63 | .ToList(); 64 | }) 65 | .Where(x => x?.Any(rss => rss.New) ?? false) 66 | .Subscribe(result => Device.BeginInvokeOnMainThread(() => rssFeed.ItemsSource = result)) 67 | .DisposeWith(SubscriptionDisposables); 68 | } 69 | 70 | Task> DownloadMultipleRss(CancellationToken ct){ 71 | return Task.Run>(async () => 72 | { 73 | 74 | System.Diagnostics.Debug.WriteLine($"Starting download at {DateTimeOffset.Now}"); 75 | 76 | var askReddit = RssDownloader.DownloadRss("https://www.reddit.com/r/AskReddit/new/.rss", ct); 77 | var todayILearned = RssDownloader.DownloadRss("https://www.reddit.com/r/todayilearned/new/.rss", ct); 78 | var news = RssDownloader.DownloadRss("https://www.reddit.com/r/news/new/.rss", ct); 79 | var worldNews = RssDownloader.DownloadRss("https://www.reddit.com/r/worldnews/new/.rss", ct); 80 | 81 | var tcs = new TaskCompletionSource(); 82 | 83 | ct.Register(() => tcs.TrySetCanceled(), false); 84 | 85 | await Task.WhenAny(Task.WhenAll(askReddit, todayILearned, news, worldNews), tcs.Task).ConfigureAwait(false); 86 | 87 | var masterList = new List(); 88 | 89 | if (!ct.IsCancellationRequested && !tcs.Task.IsCanceled) 90 | { 91 | masterList.AddRange (await askReddit.ConfigureAwait(false)); 92 | masterList.AddRange (await todayILearned.ConfigureAwait(false)); 93 | masterList.AddRange (await news.ConfigureAwait(false)); 94 | masterList.AddRange (await worldNews.ConfigureAwait(false)); 95 | } 96 | 97 | var filteredList = masterList.GroupBy (x => x.Id).Select (x => x.First ()).ToList (); 98 | 99 | if (ct.IsCancellationRequested) 100 | { 101 | System.Diagnostics.Debug.WriteLine ($"Cancelled download at {DateTimeOffset.Now}"); 102 | return Enumerable.Empty(); 103 | } 104 | 105 | return filteredList; 106 | }); 107 | } 108 | } 109 | } 110 | 111 | 112 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/SearchWithReactiveExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Concurrency; 4 | using System.Reactive.Disposables; 5 | using System.Reactive.Linq; 6 | using System.Threading.Tasks; 7 | using ReactiveExtensionExamples.Services.Api; 8 | using Splat; 9 | using Xamarin.Forms; 10 | using ReactiveUI; 11 | using System.Collections; 12 | using System.Collections.Generic; 13 | 14 | namespace ReactiveExtensionExamples.UserInterface.Pages 15 | { 16 | public class SearchWithReactiveExtensions : ContentPage 17 | { 18 | Entry textEntry; 19 | Button search; 20 | ListView searchResults; 21 | 22 | //TODO: SWRE - Item 1 - Ah, CompositeDisposable, our new friend 23 | readonly CompositeDisposable eventSubscriptions = new CompositeDisposable(); 24 | 25 | public SearchWithReactiveExtensions() { 26 | Title = "Search with Reactive Extensions"; 27 | 28 | Content = new StackLayout 29 | { 30 | Padding = new Thickness(8d), 31 | Children = { 32 | (textEntry = new Entry{ Placeholder = "Enter Search Terms" }), 33 | (search = new Button{ Text = "Search" }), 34 | (searchResults = new ListView { 35 | VerticalOptions = LayoutOptions.FillAndExpand, 36 | HorizontalOptions = LayoutOptions.FillAndExpand, 37 | HasUnevenRows = true, 38 | ItemTemplate = new DataTemplate(typeof(DuckDuckGoResultCell)) 39 | }) 40 | } 41 | }; 42 | } 43 | 44 | protected override void OnAppearing() 45 | { 46 | base.OnAppearing(); 47 | 48 | 49 | var textChangedObservable = 50 | Observable 51 | //We can convert events into observables. Here is an example of how to do it for text changing on an entry 52 | .FromEventPattern( 53 | x => textEntry.TextChanged += x, 54 | x => textEntry.TextChanged -= x) 55 | //We can use linq to filter or transform the values that are emitted from the events 56 | .Select(x => x.EventArgs.NewTextValue) 57 | //Reactive Extensions has many ways to further refine our flow of data 58 | //Here, we are going to throttle the data, this means that only the last value will be sent after a delay 59 | .Throttle(TimeSpan.FromSeconds(.75), TaskPoolScheduler.Default); 60 | 61 | 62 | var buttonClickedObservable = 63 | Observable 64 | //Again, we create observables from an event. This time is is for a button click 65 | .FromEventPattern( 66 | x => search.Clicked += x, 67 | x => search.Clicked -= x) 68 | //We don't even have to use the values that come from the event. 69 | //Here, we tell it to use the value from the text entry 70 | .Select(_ => textEntry.Text); 71 | 72 | eventSubscriptions.Add( 73 | 74 | Observable 75 | //We can merge two or more streams into a single one, this way we can process consistently 76 | .Merge(textChangedObservable, buttonClickedObservable) 77 | .Where(searchText => !string.IsNullOrWhiteSpace(searchText)) 78 | .Select(searchText => 79 | Observable 80 | .FromAsync( 81 | async cancellationToken => 82 | { 83 | var searchService = Locator.Current.GetService(); 84 | var searchResult = await searchService.Search(searchText, cancellationToken).ConfigureAwait(false); 85 | 86 | var formattedSearchResults = 87 | searchResult.RelatedTopics 88 | .Select( 89 | rt => 90 | new ViewModels.SearchResult 91 | { 92 | DisplayText = rt.Text, 93 | ImageUrl = rt?.Icon?.Url ?? string.Empty 94 | }); 95 | 96 | if (cancellationToken.IsCancellationRequested) 97 | throw new TaskCanceledException(); 98 | 99 | return formattedSearchResults; 100 | }) 101 | .Catch(Observable.Return(Enumerable.Empty()))) 102 | .Switch() 103 | //This creates the subscription and provides a way to gather our data 104 | .Subscribe( 105 | results => 106 | { 107 | Device.BeginInvokeOnMainThread(() => 108 | { 109 | try 110 | { 111 | search.IsEnabled = false; 112 | 113 | searchResults.ItemsSource = results; 114 | } 115 | finally 116 | { 117 | search.IsEnabled = true; 118 | } 119 | }); 120 | }) 121 | 122 | ); 123 | } 124 | 125 | protected override void OnDisappearing() 126 | { 127 | base.OnDisappearing(); 128 | 129 | //TODO: SWRE - Item 4 - Cleanup our subscriptions 130 | eventSubscriptions.Clear(); 131 | } 132 | 133 | class DuckDuckGoResultCell : ViewCell 134 | { 135 | public DuckDuckGoResultCell() 136 | { 137 | var grid = new Grid 138 | { 139 | Padding = new Thickness(8d), 140 | ColumnDefinitions = { 141 | new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, 142 | new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) } 143 | } 144 | }; 145 | 146 | var icon = new Image 147 | { 148 | VerticalOptions = LayoutOptions.FillAndExpand, 149 | Aspect = Aspect.AspectFit 150 | }; 151 | icon.SetBinding(Image.SourceProperty, "ImageUrl"); 152 | grid.Children.Add(icon, 0, 0); 153 | 154 | var displayText = new Label 155 | { 156 | HorizontalOptions = LayoutOptions.FillAndExpand, 157 | VerticalOptions = LayoutOptions.FillAndExpand 158 | }; 159 | displayText.SetBinding(Label.TextProperty, "DisplayText"); 160 | grid.Children.Add(displayText, 1, 0); 161 | 162 | View = grid; 163 | } 164 | } 165 | } 166 | } 167 | 168 | 169 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/StandardSearch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Linq; 4 | using System.Threading; 5 | using System.Threading.Tasks; 6 | using ReactiveExtensionExamples.Services.Api; 7 | using Splat; 8 | using Xamarin.Forms; 9 | 10 | namespace ReactiveExtensionExamples.UserInterface.Pages 11 | { 12 | public class StandardSearch : ContentPage 13 | { 14 | Entry textEntry; 15 | Button search; 16 | ListView searchResults; 17 | 18 | //TODO: SUI - Item 1 - Instance variables to manage UI state 19 | TimeSpan searchDelay = TimeSpan.FromSeconds(.75); 20 | DateTimeOffset lastSearch = DateTimeOffset.MinValue; 21 | string lastSearchTerm = string.Empty; 22 | DelayExecute searchDelayTimer; 23 | 24 | //TODO: SUI - Item 2 - Nothing special here, just UI setup 25 | public StandardSearch() { 26 | Title = "Standard Search"; 27 | 28 | Content = new StackLayout 29 | { 30 | Padding = new Thickness(8d), 31 | Children = { 32 | (textEntry = new Entry{ Placeholder = "Enter Search Terms" }), 33 | (search = new Button{ Text = "Search" }), 34 | (searchResults = new ListView { 35 | VerticalOptions = LayoutOptions.FillAndExpand, 36 | HorizontalOptions = LayoutOptions.FillAndExpand, 37 | HasUnevenRows = true, 38 | ItemTemplate = new DataTemplate(typeof(DuckDuckGoResultCell)) 39 | }) 40 | } 41 | }; 42 | } 43 | 44 | protected override void OnAppearing() 45 | { 46 | base.OnAppearing(); 47 | 48 | //TODO: SUI - Item 3 - Here is where it all starts... Event subscriptions ahoy! 49 | textEntry.TextChanged += TextEntry_TextChanged; 50 | search.Clicked += Search_Clicked; 51 | } 52 | 53 | 54 | protected override void OnDisappearing() 55 | { 56 | base.OnDisappearing(); 57 | 58 | searchDelayTimer?.Dispose(); 59 | 60 | //TODO: SUI - Item 4 - What happens if we forget to unsubscribe to these events? 61 | textEntry.TextChanged -= TextEntry_TextChanged; 62 | search.Clicked -= Search_Clicked; 63 | } 64 | 65 | 66 | //TODO: SUI - Item 5.1 - Here we are getting our method calls setup to perform the search 67 | void TextEntry_TextChanged(object sender, TextChangedEventArgs e) 68 | { 69 | searchDelayTimer?.Dispose(); 70 | searchDelayTimer = new DelayExecute(async () => await DoSearch(textEntry.Text, true), 750); 71 | } 72 | 73 | //TODO: SUI - Item 5.2 - Here we are getting our method calls setup to perform the search 74 | async void Search_Clicked(object sender, EventArgs e) 75 | { 76 | await DoSearch(textEntry.Text, false); 77 | } 78 | 79 | async Task DoSearch(string searchText, bool immediateSearch) { 80 | try 81 | { 82 | if (string.IsNullOrEmpty(searchText)) 83 | { 84 | Device.BeginInvokeOnMainThread(() => searchResults.ItemsSource = null); 85 | return; 86 | } 87 | 88 | //TODO: SUI - Item 6 - We have no stellar way to manage search delay. It needs to go somewhere 89 | if (immediateSearch) { 90 | var searchTime = DateTimeOffset.Now; 91 | if ((searchTime - lastSearch) < searchDelay || searchText.Equals(lastSearchTerm)) 92 | return; 93 | 94 | lastSearch = searchTime; 95 | lastSearchTerm = searchText; 96 | } 97 | 98 | //TODO: SUI - Item 7 - Lots of state management going on here and many opportunities to fail 99 | Device.BeginInvokeOnMainThread(() => search.IsEnabled = false); 100 | 101 | if (string.IsNullOrEmpty(searchText)) 102 | { 103 | Device.BeginInvokeOnMainThread(() => searchResults.ItemsSource = null); 104 | return; 105 | } 106 | 107 | var searchService = Locator.Current.GetService(); 108 | var searchResult = await searchService.Search(searchText); 109 | var formattedSearchResults = 110 | searchResult.RelatedTopics 111 | .Select(rt => 112 | new ViewModels.SearchResult 113 | { 114 | DisplayText = rt.Text, 115 | ImageUrl = rt?.Icon?.Url ?? string.Empty 116 | }) 117 | .ToList(); 118 | 119 | Device.BeginInvokeOnMainThread(() => searchResults.ItemsSource = formattedSearchResults); 120 | } 121 | catch (Exception) 122 | { 123 | Device.BeginInvokeOnMainThread(async () => await this.DisplayAlert("Exception", "There was a failure performing a search", "OK")); 124 | } 125 | finally 126 | { 127 | Device.BeginInvokeOnMainThread(() => search.IsEnabled = true); 128 | } 129 | } 130 | 131 | class DuckDuckGoResultCell : ViewCell 132 | { 133 | public DuckDuckGoResultCell() 134 | { 135 | var grid = new Grid 136 | { 137 | Padding = new Thickness(8d), 138 | ColumnDefinitions = { 139 | new ColumnDefinition { Width = new GridLength(1, GridUnitType.Star) }, 140 | new ColumnDefinition { Width = new GridLength(3, GridUnitType.Star) } 141 | } 142 | }; 143 | 144 | var icon = new Image 145 | { 146 | VerticalOptions = LayoutOptions.FillAndExpand, 147 | Aspect = Aspect.AspectFit 148 | }; 149 | icon.SetBinding(Image.SourceProperty, "ImageUrl"); 150 | grid.Children.Add(icon, 0, 0); 151 | 152 | var displayText = new Label 153 | { 154 | HorizontalOptions = LayoutOptions.FillAndExpand, 155 | VerticalOptions = LayoutOptions.FillAndExpand 156 | }; 157 | displayText.SetBinding(Label.TextProperty, "DisplayText"); 158 | grid.Children.Add(displayText, 1, 0); 159 | 160 | View = grid; 161 | } 162 | } 163 | 164 | internal sealed class DelayExecute : CancellationTokenSource 165 | { 166 | internal DelayExecute(Action callback, int millisecondsDueTime) 167 | { 168 | Task.Delay(millisecondsDueTime, Token).ContinueWith((t, s) => 169 | { 170 | while (!IsCancellationRequested) 171 | { 172 | callback.Invoke(); 173 | } 174 | }, null, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnRanToCompletion, TaskScheduler.Default); 175 | } 176 | 177 | protected override void Dispose(bool disposing) 178 | { 179 | if (disposing) 180 | Cancel(); 181 | 182 | base.Dispose(disposing); 183 | } 184 | } 185 | } 186 | } 187 | 188 | 189 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/Throttle.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive.Concurrency; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using Xamarin.Forms; 6 | 7 | namespace ReactiveExtensionExamples.UserInterface.Pages 8 | { 9 | public class Throttle : PageBase 10 | { 11 | Entry textEntry; 12 | StackLayout lastEntries; 13 | 14 | protected override void SetupUserInterface () 15 | { 16 | Title = "Rx - Throttle"; 17 | 18 | Content = new StackLayout { 19 | Padding = new Thickness(8d), 20 | Spacing = 16d, 21 | Children = { 22 | (textEntry = new Entry{ Placeholder = "Enter Some Text" }), 23 | new ScrollView { 24 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand, 25 | Content = (lastEntries = new StackLayout{ 26 | VerticalOptions = LayoutOptions.FillAndExpand, HorizontalOptions = LayoutOptions.FillAndExpand 27 | }) 28 | } 29 | } 30 | }; 31 | } 32 | 33 | protected override void SetupReactiveExtensions () 34 | { 35 | //This is coming from the user 36 | Observable 37 | .FromEventPattern, TextChangedEventArgs>( 38 | x => textEntry.TextChanged += x, 39 | x => textEntry.TextChanged -= x) 40 | //We want to wait 3 seconds 41 | .Throttle(TimeSpan.FromSeconds(3), TaskPoolScheduler.Default) 42 | .Select(args => args.EventArgs.NewTextValue) 43 | .Subscribe (text => { 44 | Device.BeginInvokeOnMainThread(() => { 45 | lastEntries.Children 46 | .Insert( 47 | 0, 48 | new Label { Text = text }); 49 | 50 | lastEntries.Children 51 | .Insert( 52 | 1, 53 | new Label { 54 | Text = string.Format("Received at {0:H:mm:ss}", DateTime.Now), 55 | FontAttributes = FontAttributes.Italic, 56 | FontSize = Device.GetNamedSize(NamedSize.Micro, typeof(Label)), 57 | TextColor = Color.Gray 58 | }); 59 | 60 | lastEntries.Children 61 | .Insert( 62 | 2, 63 | new BoxView { BackgroundColor = Color.Gray, HeightRequest = 2d }); 64 | }); 65 | }) 66 | .DisposeWith(SubscriptionDisposables); 67 | } 68 | } 69 | } 70 | 71 | 72 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/UserInterface/Pages/TimerUpdaterObservableEvents.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reactive; 3 | using System.Reactive.Concurrency; 4 | using System.Reactive.Disposables; 5 | using System.Reactive.Linq; 6 | using Xamarin.Forms; 7 | 8 | namespace ReactiveExtensionExamples.UserInterface.Pages 9 | { 10 | public class TimerUpdaterObservableEvents : PageBase 11 | { 12 | Label timerLabel; 13 | Button start, stop; 14 | 15 | IDisposable timerSubscription; 16 | 17 | protected override void SetupUserInterface () 18 | { 19 | start = new Button{ Text = "Start" }; 20 | stop = new Button{ Text = "Stop" }; 21 | 22 | Content = new StackLayout { 23 | Padding = new Thickness(8d), 24 | Spacing = 16d, 25 | Children = { 26 | start, 27 | stop, 28 | (timerLabel = new Label { HorizontalTextAlignment = TextAlignment.Center, Text = "Go Time" }) 29 | } 30 | }; 31 | } 32 | 33 | protected override void SetupReactiveExtensions () 34 | { 35 | var intervalObservable = 36 | Observable 37 | .Interval (TimeSpan.FromSeconds (1), TaskPoolScheduler.Default) 38 | .Select(timeInterval => string.Format ("Last Interval: {0}", timeInterval)); 39 | 40 | Observable 41 | .FromEventPattern ( 42 | x => start.Clicked += x, 43 | x => start.Clicked -= x) 44 | .Subscribe (args => { 45 | timerSubscription?.Dispose (); 46 | 47 | timerSubscription = 48 | intervalObservable 49 | .Subscribe (timeInterval => 50 | Device.BeginInvokeOnMainThread(() => timerLabel.Text = timeInterval) 51 | ); 52 | }) 53 | .DisposeWith (SubscriptionDisposables); 54 | 55 | Observable 56 | .FromEventPattern ( 57 | x => stop.Clicked += x, 58 | x => stop.Clicked -= x) 59 | .Subscribe (args => timerSubscription?.Dispose ()) 60 | .DisposeWith (SubscriptionDisposables); 61 | } 62 | 63 | protected override void OnDisappearing () 64 | { 65 | base.OnDisappearing (); 66 | 67 | timerSubscription?.Dispose (); 68 | } 69 | } 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/Values/Styles.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xamarin.Forms; 3 | using ReactiveExtensionExamples.Extensions; 4 | 5 | namespace ReactiveExtensionExamples.Values 6 | { 7 | public static class Styles 8 | { 9 | public const string 10 | ReactiveNavigation = "ReactiveNavigation", 11 | ReactiveButton = "ReactiveButton", 12 | ReactiveEntry = "ReactiveEntry", 13 | ReactiveActivityIndicator = "ReactiveActivityIndicator"; 14 | 15 | static Color 16 | Indigo = Color.FromHex("#4C108C"), 17 | MediumVioletRed = Color.FromHex("#B7178C"); 18 | 19 | public static void Initialize (){ 20 | if(Application.Current.Resources == null) 21 | Application.Current.Resources = new ResourceDictionary (); 22 | 23 | Application.Current.Resources.Add(CreateReactiveNavigationStyle ()); 24 | Application.Current.Resources.Add( CreateReactiveButtonStyle ()); 25 | Application.Current.Resources.Add(CreateReactiveEntryStyle ()); 26 | Application.Current.Resources.Add(CreateReactiveActivityIndicatorStyle ()); 27 | } 28 | 29 | static Style CreateReactiveNavigationStyle (){ 30 | return new Style (typeof(NavigationPage)) 31 | .Set (NavigationPage.BarBackgroundColorProperty, Indigo) 32 | .Set (NavigationPage.BarTextColorProperty, Color.White) 33 | .Set (NavigationPage.IconImageSourceProperty, "slideout.png") 34 | .Set(NavigationPage.IconColorProperty, Color.White); 35 | } 36 | 37 | static Style CreateReactiveButtonStyle (){ 38 | return new Style (typeof(Button)) 39 | .Set (Button.BackgroundColorProperty, MediumVioletRed) 40 | .Set (Button.TextColorProperty, Color.White) 41 | .Set (Button.MarginProperty, new Thickness(8d)); 42 | } 43 | 44 | static Style CreateReactiveEntryStyle (){ 45 | return new Style (typeof(Entry)) 46 | .Set (Entry.TextColorProperty, MediumVioletRed) 47 | .Set (Entry.MarginProperty, new Thickness(8d)); 48 | } 49 | 50 | static Style CreateReactiveActivityIndicatorStyle (){ 51 | return new Style (typeof(ActivityIndicator)) 52 | .Set (ActivityIndicator.ColorProperty, MediumVioletRed); 53 | } 54 | } 55 | } 56 | 57 | 58 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/ColorSlider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using ReactiveUI; 4 | using System.Text.RegularExpressions; 5 | using System.Drawing; 6 | using System.Reactive.Disposables; 7 | 8 | namespace ReactiveExtensionExamples.ViewModels 9 | { 10 | public class ColorSlider : ViewModelBase 11 | { 12 | int _red; 13 | public int Red 14 | { 15 | get => _red; 16 | set => this.RaiseAndSetIfChanged(ref _red, value); 17 | } 18 | 19 | int _green; 20 | public int Green { 21 | get => _green; 22 | set => this.RaiseAndSetIfChanged(ref _green, value); 23 | } 24 | 25 | int _blue; 26 | public int Blue { 27 | get => _blue; 28 | set => this.RaiseAndSetIfChanged(ref _blue, value); 29 | } 30 | 31 | ObservableAsPropertyHelper _color; 32 | public Color Color => _color?.Value ?? default(Color); 33 | 34 | public ColorSlider () 35 | { 36 | this.WhenActivated( 37 | (CompositeDisposable disposables) => 38 | { 39 | this 40 | .WhenAnyValue( 41 | x => x.Red, x => x.Green, x => x.Blue, 42 | (red, green, blue) => Color.FromArgb(255, red, green, blue)) 43 | .ToProperty(this, x => x.Color, out _color) 44 | .DisposeWith(disposables); 45 | }); 46 | } 47 | } 48 | } 49 | 50 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/DynamicData/FilterDynamicDataViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Reactive; 6 | using System.Reactive.Concurrency; 7 | using System.Reactive.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | using ReactiveExtensionExamples.Services; 12 | using ReactiveExtensionExamples.Services.Api; 13 | using ReactiveUI; 14 | using Splat; 15 | using System.Reactive.Disposables; 16 | using ReactiveUI.Legacy; 17 | using DynamicData; 18 | using System.ComponentModel; 19 | using ReactiveExtensionExamples.Models; 20 | using System.Threading; 21 | using System.Net.Http; 22 | using ReactiveUI.Fody.Helpers; 23 | using DynamicData.Aggregation; 24 | 25 | namespace ReactiveExtensionExamples.ViewModels.DynamicData 26 | { 27 | public class FilterDynamicDataViewModel : ViewModelBase 28 | { 29 | [Reactive] 30 | public string SearchQuery { get; set; } 31 | 32 | private SourceList _searchResultSource = new SourceList(); 33 | 34 | [Reactive] 35 | public IEnumerable SearchResults { get; private set; } 36 | 37 | [Reactive] 38 | public int ResultCount { get; set; } 39 | 40 | [Reactive] 41 | public ReactiveCommand Search { get; private set; } 42 | 43 | [Reactive] 44 | public ReactiveCommand CancelSearch { get; private set; } 45 | 46 | 47 | public FilterDynamicDataViewModel () 48 | { 49 | this.WhenActivated((CompositeDisposable disposables) => 50 | { 51 | _searchResultSource = new SourceList().DisposeWith(disposables); 52 | 53 | var filter = 54 | this.WhenAnyValue(x => x.SearchQuery) 55 | .SubscribeOn(RxApp.TaskpoolScheduler) 56 | .Select( 57 | search => 58 | { 59 | var searchIsEmpty = string.IsNullOrEmpty(search); 60 | 61 | return new Func( 62 | value => 63 | { 64 | if (searchIsEmpty) 65 | { 66 | return true; 67 | } 68 | 69 | return FuzzySharp.Fuzz.PartialRatio(value.Title, search) > 75; 70 | }); 71 | }); 72 | 73 | var filteredData = 74 | _searchResultSource 75 | .Connect() 76 | .SubscribeOn(RxApp.MainThreadScheduler) 77 | .Filter(filter) 78 | .Publish() 79 | .RefCount(); 80 | 81 | filteredData 82 | .ObserveOn(RxApp.MainThreadScheduler) 83 | .Bind(out var searchResultsBinding) 84 | .Subscribe() 85 | .DisposeWith(disposables); 86 | 87 | this.SearchResults = searchResultsBinding; 88 | 89 | filteredData 90 | .Count() 91 | .BindTo(this, x => x.ResultCount) 92 | .DisposeWith(disposables); 93 | 94 | CancelSearch = 95 | ReactiveCommand 96 | .Create(() => {}) 97 | .DisposeWith(disposables); 98 | 99 | Search = 100 | ReactiveCommand 101 | .CreateFromObservable( 102 | () => 103 | { 104 | return Observable 105 | .StartAsync( 106 | async (ct) => 107 | { 108 | var worldNews = await RssDownloader.DownloadRss("https://www.reddit.com/r/worldnews/new/.rss", ct).ConfigureAwait(false); 109 | 110 | if(ct.IsCancellationRequested) 111 | { 112 | return; 113 | } 114 | 115 | _searchResultSource 116 | .Edit( 117 | innerList => 118 | { 119 | innerList.Clear(); 120 | innerList.AddRange(worldNews); 121 | }); 122 | }) 123 | .TakeUntil(this.CancelSearch) 124 | .SelectUnit(); 125 | }) 126 | .DisposeWith(disposables); 127 | 128 | Observable 129 | .Merge( 130 | this.ThrownExceptions, 131 | Search.ThrownExceptions) 132 | .Do(ex => System.Diagnostics.Debug.WriteLine($"ERROR!!: {ex}")); 133 | }); 134 | 135 | } 136 | } 137 | } -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/DynamicData/SimpleDynamicDataViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Reactive; 6 | using System.Reactive.Concurrency; 7 | using System.Reactive.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | using ReactiveExtensionExamples.Services; 12 | using ReactiveExtensionExamples.Services.Api; 13 | using ReactiveUI; 14 | using Splat; 15 | using System.Reactive.Disposables; 16 | using ReactiveUI.Legacy; 17 | using DynamicData; 18 | using System.ComponentModel; 19 | using ReactiveExtensionExamples.Models; 20 | using System.Threading; 21 | using System.Net.Http; 22 | using ReactiveUI.Fody.Helpers; 23 | 24 | namespace ReactiveExtensionExamples.ViewModels.DynamicData 25 | { 26 | public class SimpleDynamicDataViewModel : ViewModelBase 27 | { 28 | private SourceList _searchResultSource = new SourceList(); 29 | 30 | [Reactive] 31 | public IEnumerable SearchResults { get; private set; } 32 | 33 | [Reactive] 34 | public ReactiveCommand Search { get; private set; } 35 | 36 | public SimpleDynamicDataViewModel () 37 | { 38 | this.WhenActivated((CompositeDisposable disposables) => 39 | { 40 | _searchResultSource = 41 | new SourceList() 42 | .DisposeWith(disposables); 43 | 44 | _searchResultSource 45 | .Connect() 46 | .ObserveOn(RxApp.MainThreadScheduler) 47 | .Bind(out var searchResultsBinding) 48 | .Subscribe() 49 | .DisposeWith(disposables); 50 | 51 | this.SearchResults = searchResultsBinding; 52 | 53 | Search = 54 | ReactiveCommand 55 | .CreateFromTask( 56 | async (ct) => 57 | { 58 | var worldNews = 59 | await RssDownloader 60 | .DownloadRss("https://www.reddit.com/r/worldnews/new/.rss", ct) 61 | .ConfigureAwait(false); 62 | 63 | _searchResultSource 64 | .Edit( 65 | innerList => 66 | { 67 | innerList.Clear(); 68 | innerList.AddRange(worldNews); 69 | }); 70 | }) 71 | .DisposeWith(disposables); 72 | }); 73 | 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/DynamicData/SortDynamicData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Reactive; 6 | using System.Reactive.Concurrency; 7 | using System.Reactive.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | using ReactiveExtensionExamples.Services; 12 | using ReactiveExtensionExamples.Services.Api; 13 | using ReactiveUI; 14 | using Splat; 15 | using System.Reactive.Disposables; 16 | using ReactiveUI.Legacy; 17 | using DynamicData; 18 | using System.ComponentModel; 19 | using ReactiveExtensionExamples.Models; 20 | using System.Threading; 21 | using System.Net.Http; 22 | using ReactiveUI.Fody.Helpers; 23 | using DynamicData.Aggregation; 24 | using DynamicData.Binding; 25 | 26 | namespace ReactiveExtensionExamples.ViewModels.DynamicData 27 | { 28 | public enum SortType 29 | { 30 | TitleAscending, 31 | TitleDescending, 32 | DateTimeAscending, 33 | DateTimeDescending, 34 | } 35 | 36 | public class SortDynamicDataViewModel : ViewModelBase 37 | { 38 | [Reactive] 39 | public SortType SelectedSortType { get; set; } 40 | 41 | private SourceList _searchResultSource = new SourceList(); 42 | 43 | [Reactive] 44 | public IEnumerable SearchResults { get; private set; } 45 | 46 | [Reactive] 47 | public ReactiveCommand Search { get; private set; } 48 | 49 | public SortDynamicDataViewModel () 50 | { 51 | this.WhenActivated((CompositeDisposable disposables) => 52 | { 53 | _searchResultSource = new SourceList().DisposeWith(disposables); 54 | 55 | var sorter = 56 | this.WhenAnyValue(x => x.SelectedSortType) 57 | .Select( 58 | SelectedSortType => 59 | { 60 | switch (SelectedSortType) 61 | { 62 | case SortType.DateTimeAscending: 63 | return SortExpressionComparer.Ascending(x => x.Updated); 64 | case SortType.DateTimeDescending: 65 | return SortExpressionComparer.Descending(x => x.Updated); 66 | case SortType.TitleAscending: 67 | return SortExpressionComparer.Ascending(x => x.Title); 68 | case SortType.TitleDescending: 69 | default: 70 | return SortExpressionComparer.Descending(x => x.Title); 71 | } 72 | }); 73 | 74 | _searchResultSource 75 | .Connect() 76 | .SubscribeOn(RxApp.TaskpoolScheduler) 77 | .Sort(sorter) 78 | .ObserveOn(RxApp.MainThreadScheduler) 79 | .Bind(out var searchResultsBinding) 80 | .Subscribe() 81 | .DisposeWith(disposables); 82 | 83 | this.SearchResults = searchResultsBinding; 84 | 85 | Search = 86 | ReactiveCommand 87 | .CreateFromTask( 88 | async (ct) => 89 | { 90 | var worldNews = await RssDownloader.DownloadRss("https://www.reddit.com/r/worldnews/new/.rss", ct).ConfigureAwait(false); 91 | 92 | _searchResultSource 93 | .Edit( 94 | innerList => 95 | { 96 | innerList.Clear(); 97 | innerList.AddRange(worldNews); 98 | }); 99 | }) 100 | .DisposeWith(disposables); 101 | }); 102 | 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/DynamicData/SourceCacheDynamicDataViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Reactive; 6 | using System.Reactive.Concurrency; 7 | using System.Reactive.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | using ReactiveExtensionExamples.Services; 12 | using ReactiveExtensionExamples.Services.Api; 13 | using ReactiveUI; 14 | using Splat; 15 | using System.Reactive.Disposables; 16 | using ReactiveUI.Legacy; 17 | using DynamicData; 18 | using System.ComponentModel; 19 | using ReactiveExtensionExamples.Models; 20 | using System.Threading; 21 | using System.Net.Http; 22 | using ReactiveUI.Fody.Helpers; 23 | using DynamicData.Aggregation; 24 | using DynamicData.Binding; 25 | 26 | namespace ReactiveExtensionExamples.ViewModels.DynamicData 27 | { 28 | public class SourceCacheDynamicDataViewModel : ViewModelBase 29 | { 30 | [Reactive] 31 | public string SearchQuery { get; set; } 32 | 33 | private SourceCache _searchResultSource; 34 | 35 | [Reactive] 36 | public IEnumerable SearchResults { get; private set; } 37 | 38 | [Reactive] 39 | public int ResultCount { get; set; } 40 | 41 | [Reactive] 42 | public ReactiveCommand Search { get; private set; } 43 | 44 | public SourceCacheDynamicDataViewModel () 45 | { 46 | this.WhenActivated((CompositeDisposable disposables) => 47 | { 48 | _searchResultSource = new SourceCache(x => x.Id).DisposeWith(disposables); 49 | 50 | _searchResultSource 51 | .Connect() 52 | .SubscribeOn(RxApp.TaskpoolScheduler) 53 | .OnItemAdded(x => x.New = true) 54 | .OnItemUpdated((current, previous) => current.New = false) 55 | .Sort( 56 | SortExpressionComparer 57 | .Descending(x => x.New) 58 | .ThenByDescending(x => x.Updated)) 59 | .ObserveOn(RxApp.MainThreadScheduler) 60 | .Bind(out var searchResultsBinding) 61 | .Subscribe() 62 | .DisposeWith(disposables); 63 | 64 | this.SearchResults = searchResultsBinding; 65 | 66 | Search = 67 | ReactiveCommand 68 | .CreateFromTask( 69 | async (ct) => 70 | { 71 | var rss = await RssDownloader.DownloadRss("https://www.reddit.com/r/worldnews/new/.rss", ct).ConfigureAwait(false); 72 | 73 | _searchResultSource.AddOrUpdate(rss); 74 | }) 75 | .DisposeWith(disposables); 76 | 77 | Observable 78 | .Interval(TimeSpan.FromSeconds(5)) 79 | .SelectUnit() 80 | .InvokeCommand(this, x => x.Search) 81 | .DisposeWith(disposables); 82 | }); 83 | 84 | } 85 | } 86 | } -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/LoginCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reactive.Linq; 4 | using ReactiveUI; 5 | using System.Text.RegularExpressions; 6 | using System.Reactive; 7 | using System.Threading.Tasks; 8 | 9 | namespace ReactiveExtensionExamples.ViewModels 10 | { 11 | public class Login : ReactiveObject 12 | { 13 | string _emailAddress; 14 | public string EmailAddress { 15 | get { return _emailAddress; } 16 | set { this.RaiseAndSetIfChanged (ref _emailAddress, value); } 17 | } 18 | 19 | string _password; 20 | public string Password { 21 | get { return _password; } 22 | set { this.RaiseAndSetIfChanged (ref _password, value); } 23 | } 24 | 25 | ObservableAsPropertyHelper _isLoading; 26 | 27 | public bool IsLoading { 28 | get { return _isLoading?.Value ?? false; } 29 | } 30 | 31 | ObservableAsPropertyHelper _isValid; 32 | public bool IsValid { 33 | get { return _isValid?.Value ?? false; } 34 | } 35 | 36 | public ReactiveCommand PerformLogin; 37 | 38 | public Login () 39 | { 40 | this.WhenAnyValue (e => e.EmailAddress, p => p.Password, 41 | (emailAddress, password) => 42 | /* Validate our email address */ 43 | ( 44 | !string.IsNullOrEmpty(emailAddress) 45 | && 46 | Regex.Matches(emailAddress, "^\\w+([-+.']\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$").Count == 1 47 | ) 48 | && 49 | /* Validate our password */ 50 | ( 51 | !string.IsNullOrEmpty(password) 52 | && 53 | password.Length > 5 54 | )) 55 | .ToProperty(this, v => v.IsValid, out _isValid); 56 | 57 | var canExecuteLogin = 58 | Observable 59 | .CombineLatest( 60 | this.WhenAnyValue(x => x.IsLoading), 61 | this.WhenAnyValue(x => x.IsValid), 62 | (isLoading, isValid) => !isLoading && isValid) 63 | .Do(x => System.Diagnostics.Debug.WriteLine($"Can Login: {x}")); 64 | 65 | PerformLogin = 66 | ReactiveCommand 67 | .CreateFromTask( 68 | async _ => 69 | { 70 | var random = new Random(Guid.NewGuid().GetHashCode()); 71 | await Task.Delay (random.Next(250, 10000)) /* Fake Web Service Call */; 72 | 73 | return Unit.Default; 74 | }, 75 | canExecuteLogin); 76 | 77 | this.WhenAnyObservable(x => x.PerformLogin.IsExecuting) 78 | .StartWith(false) 79 | .ToProperty (this, x => x.IsLoading, out _isLoading); 80 | } 81 | } 82 | } 83 | 84 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/SearchViewModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.ObjectModel; 4 | using System.Linq; 5 | using System.Reactive; 6 | using System.Reactive.Concurrency; 7 | using System.Reactive.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | using System.Xml.Linq; 11 | using ReactiveExtensionExamples.Services; 12 | using ReactiveExtensionExamples.Services.Api; 13 | using ReactiveUI; 14 | using Splat; 15 | using System.Reactive.Disposables; 16 | using ReactiveUI.Legacy; 17 | using DynamicData; 18 | using System.ComponentModel; 19 | 20 | namespace ReactiveExtensionExamples.ViewModels 21 | { 22 | public class SearchViewModel : ViewModelBase 23 | { 24 | 25 | string _searchQuery; 26 | 27 | public string SearchQuery 28 | { 29 | get { return _searchQuery; } 30 | set { this.RaiseAndSetIfChanged(ref _searchQuery, value); } 31 | } 32 | 33 | private SourceList _searchResultSource = new SourceList(); 34 | 35 | ReadOnlyObservableCollection _searchResults; 36 | 37 | public ReadOnlyObservableCollection SearchResults 38 | { 39 | get => _searchResults; 40 | private set => this.RaiseAndSetIfChanged(ref _searchResults, value); 41 | } 42 | 43 | ReactiveCommand> _search; 44 | public ReactiveCommand> Search 45 | { 46 | get { return _search; } 47 | private set { this.RaiseAndSetIfChanged(ref _search, value); } 48 | } 49 | 50 | public Interaction SearchError { get; private set; } = new Interaction(); 51 | 52 | public SearchViewModel() 53 | { 54 | this.WhenActivated((CompositeDisposable disposables) => 55 | { 56 | _searchResultSource = new SourceList().DisposeWith(disposables); 57 | 58 | _searchResultSource 59 | .Connect() 60 | .ObserveOn(RxApp.MainThreadScheduler) 61 | .Bind(out var searchResultsBinding) 62 | .Subscribe() 63 | .DisposeWith(disposables); 64 | 65 | this.SearchResults = searchResultsBinding; 66 | 67 | // ReactiveCommand has built-in support for background operations and 68 | // guarantees that this block will only run exactly once at a time, and 69 | // that the CanExecute will auto-disable and that property IsExecuting will 70 | // be set according whilst it is running. 71 | Search = 72 | ReactiveCommand 73 | .CreateFromTask>( 74 | // Here we're describing here, in a *declarative way*, the conditions in 75 | // which the Search command is enabled. Now our Command IsEnabled is 76 | // perfectly efficient, because we're only updating the UI in the scenario 77 | // when it should change. 78 | async searchQuery => 79 | { 80 | var random = new Random(Guid.NewGuid().GetHashCode()); 81 | await Task.Delay(random.Next(250, 2500)); 82 | 83 | if(string.IsNullOrEmpty(searchQuery)) 84 | { 85 | return Enumerable.Empty(); 86 | } 87 | 88 | //This is just here so simulate a network type exception 89 | if (DateTime.Now.Second % 9 == 0) 90 | throw new TimeoutException("Unable to connect to web service"); 91 | 92 | var searchService = Locator.Current.GetService(); 93 | var searchResult = await searchService.Search(searchQuery); 94 | 95 | return searchResult 96 | .RelatedTopics 97 | .Select(rt => 98 | new SearchResult 99 | { 100 | DisplayText = rt.Text, 101 | ImageUrl = rt?.Icon?.Url ?? string.Empty 102 | }) 103 | .ToList(); 104 | }) 105 | .DisposeWith(disposables); 106 | 107 | //// ReactiveCommands are themselves IObservables, whose value are the results 108 | //// from the async method, guaranteed to arrive on the given scheduler. 109 | //// We're going to take the list of search results that the background 110 | //// operation loaded, and them into our SearchResults. 111 | Search 112 | .ObserveOn(RxApp.MainThreadScheduler) 113 | .Subscribe(searchResults => 114 | { 115 | _searchResultSource 116 | .Edit( 117 | list => 118 | { 119 | list.Clear(); 120 | list.AddRange(searchResults); 121 | }); 122 | }) 123 | .DisposeWith(disposables); 124 | 125 | // ThrownExceptions is any exception thrown from the CreateFromObservable piped 126 | // to this Observable. Subscribing to this allows you to handle errors on 127 | // the UI thread. 128 | Search 129 | .ThrownExceptions 130 | .Subscribe(async ex => 131 | { 132 | await SearchError.Handle("Potential Network Connectivity Error"); 133 | }) 134 | .DisposeWith(disposables); 135 | 136 | //Behaviors 137 | this 138 | .WhenAnyValue(x => x.SearchQuery) 139 | .Throttle(TimeSpan.FromSeconds(.75), TaskPoolScheduler.Default) 140 | .Do(x => System.Diagnostics.Debug.WriteLine($"Throttle fired for {x}")) 141 | .ObserveOn(RxApp.MainThreadScheduler) 142 | .InvokeCommand(Search) 143 | .DisposeWith(disposables); 144 | }); 145 | 146 | } 147 | } 148 | 149 | public class SearchResult : ReactiveObject 150 | { 151 | 152 | string _imageUrl; 153 | 154 | public string ImageUrl 155 | { 156 | get { return _imageUrl; } 157 | set { this.RaiseAndSetIfChanged(ref _imageUrl, value); } 158 | } 159 | 160 | string _displayText; 161 | 162 | public string DisplayText 163 | { 164 | get { return _displayText; } 165 | set { this.RaiseAndSetIfChanged(ref _displayText, value); } 166 | } 167 | 168 | } 169 | } -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/ViewModelBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using ReactiveUI; 3 | namespace ReactiveExtensionExamples.ViewModels 4 | { 5 | public class ViewModelBase : ReactiveObject, IActivatableViewModel 6 | { 7 | protected readonly ViewModelActivator viewModelActivator = new ViewModelActivator(); 8 | public ViewModelActivator Activator 9 | { 10 | get { return viewModelActivator; } 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ReactiveExtensionExamples/ViewModels/XamarinEssentials.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using ReactiveUI; 3 | using System.Reactive.Disposables; 4 | using System.Reactive.Linq; 5 | using Xamarin.Essentials; 6 | using System.Numerics; 7 | using System; 8 | using System.Drawing; 9 | 10 | namespace ReactiveExtensionExamples.ViewModels 11 | { 12 | public class XamarinEssentials : ViewModelBase 13 | { 14 | ObservableAsPropertyHelper _x; 15 | public double X => _x.Value; 16 | 17 | ObservableAsPropertyHelper _y; 18 | public double Y => _y.Value; 19 | 20 | ObservableAsPropertyHelper _z; 21 | public double Z => _z.Value; 22 | 23 | ObservableAsPropertyHelper _color; 24 | public Color Color => _color?.Value ?? default(Color); 25 | 26 | public XamarinEssentials () 27 | { 28 | this.WhenActivated( 29 | (CompositeDisposable disposables) => 30 | { 31 | var gyroscopeChanged = 32 | Observable 33 | .FromEvent, AccelerometerChangedEventArgs>( 34 | x => Accelerometer.ReadingChanged += x, 35 | x => Accelerometer.ReadingChanged -= x) 36 | .SubscribeOn(RxApp.TaskpoolScheduler) 37 | .Select(x => x.Reading.Acceleration) 38 | .StartWith(Vector3.Zero) 39 | .Do(x => 40 | { 41 | if (!Accelerometer.IsMonitoring) 42 | Accelerometer.Start(SensorSpeed.UI); 43 | }) 44 | .Finally(() => { 45 | try 46 | { 47 | Accelerometer.Stop(); 48 | } 49 | catch (FeatureNotSupportedException) 50 | { 51 | 52 | } 53 | }) 54 | .Throttle(TimeSpan.FromMilliseconds(20)) 55 | .Do(x => System.Diagnostics.Debug.WriteLine($"ACC: {x}")) 56 | .Publish() 57 | .RefCount(); 58 | 59 | gyroscopeChanged 60 | .Select(x => (double)x.X) 61 | .ToProperty(this, x => x.X, out _x) 62 | .DisposeWith(disposables); 63 | 64 | gyroscopeChanged 65 | .Select(x => (double)x.Y) 66 | .ToProperty(this, x => x.Y, out _y) 67 | .DisposeWith(disposables); 68 | 69 | gyroscopeChanged 70 | .Select(x => (double)x.Z) 71 | .ToProperty(this, x => x.Z, out _z) 72 | .DisposeWith(disposables); 73 | 74 | gyroscopeChanged 75 | .Select(x => { 76 | var r = (int)Math.Min(255, Math.Abs(x.X * 255)); 77 | var g = (int)Math.Min(255, Math.Abs(x.Y * 255)); 78 | var b = (int)Math.Min(255, Math.Abs(x.Z * 255)); 79 | return Color.FromArgb(r, g, b); 80 | }) 81 | .ToProperty(this, x => x.Color, out _color) 82 | .DisposeWith(disposables); 83 | 84 | }); 85 | } 86 | } 87 | } 88 | 89 | -------------------------------------------------------------------------------- /ReadMe.md: -------------------------------------------------------------------------------- 1 | Reactive Examples in Xamarin 2 | ============================ 3 | This repository contains examples of using the [Microsoft Reactive Extensions (Rx)](https://msdn.microsoft.com/en-us/data/gg577609.aspx), specifically the [Rx.NET](https://github.com/Reactive-Extensions/Rx.NET) extensions in a Xamarin application with Android and iOS implementations. 4 | 5 | These examples support the talk [Why You Should Be Building Better Mobile Apps with Reactive Programming](https://evolve.xamarin.com/session/56e20424bad314273ca4d815), also available on [YouTube](https://www.youtube.com/watch?v=DYEbUF4xs1Q) directly, as presented at [Xamarin Evolve](https://evolve.xamarin.com/) 2016 and Future Decoded 2016 by Michael Stonis. 6 | 7 | The examples include several variations on reacting to text field updates, working with a color slider to change values, and scenarios for fetching internet data and dealing with server response times and reliability. 8 | -------------------------------------------------------------------------------- /iOS/AppDelegate.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | 5 | using Foundation; 6 | using UIKit; 7 | 8 | namespace ReactiveExtensionExamples.iOS 9 | { 10 | [Register ("AppDelegate")] 11 | public partial class AppDelegate : global::Xamarin.Forms.Platform.iOS.FormsApplicationDelegate 12 | { 13 | public override bool FinishedLaunching (UIApplication app, NSDictionary options) 14 | { 15 | global::Xamarin.Forms.Forms.Init (); 16 | 17 | LoadApplication (new App ()); 18 | 19 | return base.FinishedLaunching (app, options); 20 | } 21 | } 22 | } 23 | 24 | -------------------------------------------------------------------------------- /iOS/Entitlements.plist: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iOS/ITunesArtwork: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/ITunesArtwork -------------------------------------------------------------------------------- /iOS/ITunesArtwork@2x: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/ITunesArtwork@2x -------------------------------------------------------------------------------- /iOS/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleIdentifier 6 | com.eightbot.ReactiveExtensionExamples.iOS 7 | CFBundleShortVersionString 8 | 1.0 9 | CFBundleVersion 10 | 1.0 11 | LSRequiresIPhoneOS 12 | 13 | MinimumOSVersion 14 | 9.0 15 | UIDeviceFamily 16 | 17 | 1 18 | 2 19 | 20 | UIRequiredDeviceCapabilities 21 | 22 | armv7 23 | 24 | UISupportedInterfaceOrientations 25 | 26 | UIInterfaceOrientationPortrait 27 | UIInterfaceOrientationLandscapeLeft 28 | UIInterfaceOrientationLandscapeRight 29 | 30 | UISupportedInterfaceOrientations~ipad 31 | 32 | UIInterfaceOrientationPortrait 33 | UIInterfaceOrientationPortraitUpsideDown 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UILaunchStoryboardName 38 | LaunchScreen 39 | XSAppIconAssets 40 | Resources/Images.xcassets/AppIcons.appiconset 41 | UIStatusBarStyle 42 | UIStatusBarStyleLightContent 43 | UIViewControllerBasedStatusBarAppearance 44 | 45 | CFBundleName 46 | Rx Examples 47 | 48 | 49 | -------------------------------------------------------------------------------- /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 ReactiveExtensionExamples.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, typeof(AppDelegate)); 18 | } 19 | } 20 | } 21 | 22 | -------------------------------------------------------------------------------- /iOS/Resources/Default-568h@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Default-568h@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Default-Portrait.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Default-Portrait.png -------------------------------------------------------------------------------- /iOS/Resources/Default-Portrait@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Default-Portrait@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Default.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Default.png -------------------------------------------------------------------------------- /iOS/Resources/Default@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Default@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Icon-60@3x.png -------------------------------------------------------------------------------- /iOS/Resources/Icon-Small-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Icon-Small-40@3x.png -------------------------------------------------------------------------------- /iOS/Resources/Icon-Small@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Icon-Small@3x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images": [ 3 | { 4 | "filename": "reactive_logo-29.png", 5 | "size": "29x29", 6 | "scale": "1x", 7 | "idiom": "iphone" 8 | }, 9 | { 10 | "filename": "reactive_logo-29@2x.png", 11 | "size": "29x29", 12 | "scale": "2x", 13 | "idiom": "iphone" 14 | }, 15 | { 16 | "filename": "reactive_logo-29@3x.png", 17 | "size": "29x29", 18 | "scale": "3x", 19 | "idiom": "iphone" 20 | }, 21 | { 22 | "filename": "reactive_logo-40@2x.png", 23 | "size": "40x40", 24 | "scale": "2x", 25 | "idiom": "iphone" 26 | }, 27 | { 28 | "filename": "reactive_logo-40@3x.png", 29 | "size": "40x40", 30 | "scale": "3x", 31 | "idiom": "iphone" 32 | }, 33 | { 34 | "size": "57x57", 35 | "scale": "1x", 36 | "idiom": "iphone" 37 | }, 38 | { 39 | "size": "57x57", 40 | "scale": "2x", 41 | "idiom": "iphone" 42 | }, 43 | { 44 | "filename": "reactive_logo-60@2x.png", 45 | "size": "60x60", 46 | "scale": "2x", 47 | "idiom": "iphone" 48 | }, 49 | { 50 | "filename": "reactive_logo-60@3x.png", 51 | "size": "60x60", 52 | "scale": "3x", 53 | "idiom": "iphone" 54 | }, 55 | { 56 | "filename": "reactive_logo-29.png", 57 | "size": "29x29", 58 | "scale": "1x", 59 | "idiom": "ipad" 60 | }, 61 | { 62 | "filename": "reactive_logo-29@2x.png", 63 | "size": "29x29", 64 | "scale": "2x", 65 | "idiom": "ipad" 66 | }, 67 | { 68 | "filename": "reactive_logo-40.png", 69 | "size": "40x40", 70 | "scale": "1x", 71 | "idiom": "ipad" 72 | }, 73 | { 74 | "filename": "reactive_logo-40@2x.png", 75 | "size": "40x40", 76 | "scale": "2x", 77 | "idiom": "ipad" 78 | }, 79 | { 80 | "size": "50x50", 81 | "scale": "1x", 82 | "idiom": "ipad" 83 | }, 84 | { 85 | "size": "50x50", 86 | "scale": "2x", 87 | "idiom": "ipad" 88 | }, 89 | { 90 | "size": "83.5x83.5", 91 | "scale": "2x", 92 | "idiom": "ipad" 93 | }, 94 | { 95 | "size": "72x72", 96 | "scale": "1x", 97 | "idiom": "ipad" 98 | }, 99 | { 100 | "size": "72x72", 101 | "scale": "2x", 102 | "idiom": "ipad" 103 | }, 104 | { 105 | "filename": "reactive_logo-76.png", 106 | "size": "76x76", 107 | "scale": "1x", 108 | "idiom": "ipad" 109 | }, 110 | { 111 | "filename": "reactive_logo-76@2x.png", 112 | "size": "76x76", 113 | "scale": "2x", 114 | "idiom": "ipad" 115 | }, 116 | { 117 | "size": "60x60", 118 | "scale": "2x", 119 | "idiom": "car" 120 | }, 121 | { 122 | "size": "60x60", 123 | "scale": "3x", 124 | "idiom": "car" 125 | }, 126 | { 127 | "role": "notificationCenter", 128 | "size": "24x24", 129 | "subtype": "38mm", 130 | "scale": "2x", 131 | "idiom": "watch" 132 | }, 133 | { 134 | "role": "notificationCenter", 135 | "size": "27.5x27.5", 136 | "subtype": "42mm", 137 | "scale": "2x", 138 | "idiom": "watch" 139 | }, 140 | { 141 | "role": "companionSettings", 142 | "size": "29x29", 143 | "scale": "2x", 144 | "idiom": "watch" 145 | }, 146 | { 147 | "role": "companionSettings", 148 | "size": "29x29", 149 | "scale": "3x", 150 | "idiom": "watch" 151 | }, 152 | { 153 | "role": "appLauncher", 154 | "size": "40x40", 155 | "subtype": "38mm", 156 | "scale": "2x", 157 | "idiom": "watch" 158 | }, 159 | { 160 | "role": "longLook", 161 | "size": "44x44", 162 | "subtype": "42mm", 163 | "scale": "2x", 164 | "idiom": "watch" 165 | }, 166 | { 167 | "role": "quickLook", 168 | "size": "86x86", 169 | "subtype": "38mm", 170 | "scale": "2x", 171 | "idiom": "watch" 172 | }, 173 | { 174 | "role": "quickLook", 175 | "size": "98x98", 176 | "subtype": "42mm", 177 | "scale": "2x", 178 | "idiom": "watch" 179 | }, 180 | { 181 | "size": "16x16", 182 | "scale": "1x", 183 | "idiom": "mac" 184 | }, 185 | { 186 | "size": "16x16", 187 | "scale": "2x", 188 | "idiom": "mac" 189 | }, 190 | { 191 | "size": "32x32", 192 | "scale": "1x", 193 | "idiom": "mac" 194 | }, 195 | { 196 | "size": "32x32", 197 | "scale": "2x", 198 | "idiom": "mac" 199 | }, 200 | { 201 | "size": "128x128", 202 | "scale": "1x", 203 | "idiom": "mac" 204 | }, 205 | { 206 | "size": "128x128", 207 | "scale": "2x", 208 | "idiom": "mac" 209 | }, 210 | { 211 | "size": "256x256", 212 | "scale": "1x", 213 | "idiom": "mac" 214 | }, 215 | { 216 | "size": "256x256", 217 | "scale": "2x", 218 | "idiom": "mac" 219 | }, 220 | { 221 | "size": "512x512", 222 | "scale": "1x", 223 | "idiom": "mac" 224 | }, 225 | { 226 | "size": "512x512", 227 | "scale": "2x", 228 | "idiom": "mac" 229 | } 230 | ], 231 | "info": { 232 | "version": 1, 233 | "author": "xcode" 234 | } 235 | } -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-60@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-76.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-76@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small-40.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small-40@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/Icon-Small@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-29.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-29@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-29@3x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-40.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-40@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-40@3x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-60@2x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-60@3x.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-76.png -------------------------------------------------------------------------------- /iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/Images.xcassets/AppIcons.appiconset/reactive_logo-76@2x.png -------------------------------------------------------------------------------- /iOS/Resources/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 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "29x29", 5 | "idiom" : "iphone", 6 | "filename" : "reactive_logo-29@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "29x29", 11 | "idiom" : "iphone", 12 | "filename" : "reactive_logo-29@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "40x40", 17 | "idiom" : "iphone", 18 | "filename" : "reactive_logo-40@2x.png", 19 | "scale" : "2x" 20 | }, 21 | { 22 | "size" : "40x40", 23 | "idiom" : "iphone", 24 | "filename" : "reactive_logo-40@3x.png", 25 | "scale" : "3x" 26 | }, 27 | { 28 | "size" : "60x60", 29 | "idiom" : "iphone", 30 | "filename" : "reactive_logo-60@2x.png", 31 | "scale" : "2x" 32 | }, 33 | { 34 | "size" : "60x60", 35 | "idiom" : "iphone", 36 | "filename" : "reactive_logo-60@3x.png", 37 | "scale" : "3x" 38 | }, 39 | { 40 | "size" : "29x29", 41 | "idiom" : "ipad", 42 | "filename" : "reactive_logo-29.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "29x29", 47 | "idiom" : "ipad", 48 | "filename" : "reactive_logo-29@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "40x40", 53 | "idiom" : "ipad", 54 | "filename" : "reactive_logo-40.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "40x40", 59 | "idiom" : "ipad", 60 | "filename" : "reactive_logo-40@2x.png", 61 | "scale" : "2x" 62 | }, 63 | { 64 | "size" : "76x76", 65 | "idiom" : "ipad", 66 | "filename" : "reactive_logo-76.png", 67 | "scale" : "1x" 68 | }, 69 | { 70 | "size" : "76x76", 71 | "idiom" : "ipad", 72 | "filename" : "reactive_logo-76@2x.png", 73 | "scale" : "2x" 74 | } 75 | ], 76 | "info" : { 77 | "version" : 1, 78 | "author" : "xcode" 79 | } 80 | } -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-29.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-29@2x.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-29@3x.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-40.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-40@2x.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-40@3x.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-60@2x.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-60@3x.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-76.png -------------------------------------------------------------------------------- /iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/iOS/AppIcon.appiconset/reactive_logo-76@2x.png -------------------------------------------------------------------------------- /iOS/Resources/reactive_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/reactive_logo.png -------------------------------------------------------------------------------- /iOS/Resources/reactive_logo@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/reactive_logo@2x.png -------------------------------------------------------------------------------- /iOS/Resources/reactive_logo@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/reactive_logo@3x.png -------------------------------------------------------------------------------- /iOS/Resources/slideout.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/slideout.png -------------------------------------------------------------------------------- /iOS/Resources/slideout@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/TheEightBot/Reactive-Examples/203a224f276091ba599fc710a162a9b2efc2c62f/iOS/Resources/slideout@2x.png -------------------------------------------------------------------------------- /iOS/iOS.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Debug 5 | iPhoneSimulator 6 | {FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 7 | {ECB1239A-8DAD-4E69-A4D2-D879D46491F7} 8 | Exe 9 | EightBot.ReactiveExtensionExamples.iOS 10 | Resources 11 | EightBot.ReactiveExtensionExamples.iOS 12 | 13 | 14 | true 15 | full 16 | false 17 | bin\iPhoneSimulator\Debug 18 | DEBUG; 19 | prompt 20 | 4 21 | false 22 | i386, x86_64 23 | None 24 | true 25 | true 26 | NSUrlSessionHandler 27 | iPhone Developer 28 | true 29 | true 30 | 31 | 32 | full 33 | true 34 | bin\iPhoneSimulator\Release 35 | prompt 36 | 4 37 | i386 38 | false 39 | None 40 | NSUrlSessionHandler 41 | 42 | 43 | true 44 | full 45 | false 46 | bin\iPhone\Debug 47 | DEBUG; 48 | prompt 49 | 4 50 | false 51 | ARMv7, ARM64 52 | Entitlements.plist 53 | true 54 | iPhone Developer 55 | true 56 | 57 | 58 | full 59 | true 60 | bin\iPhone\Release 61 | prompt 62 | 4 63 | Entitlements.plist 64 | ARMv7, ARM64 65 | false 66 | iPhone Distribution 67 | Automatic:AppStore 68 | 69 | 70 | full 71 | true 72 | bin\iPhone\Ad-Hoc 73 | prompt 74 | 4 75 | false 76 | ARMv7, ARM64 77 | Entitlements.plist 78 | true 79 | iPhone Distribution 80 | Automatic:AdHoc 81 | true 82 | NSUrlSessionHandler 83 | 84 | 85 | full 86 | true 87 | bin\iPhone\AppStore 88 | prompt 89 | 4 90 | false 91 | ARMv7, ARM64 92 | Entitlements.plist 93 | Automatic:AppStore 94 | iPhone Distribution 95 | NSUrlSessionHandler 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 5.0.0.2401 105 | 106 | 107 | 18.0.7 108 | 109 | 110 | 18.0.7 111 | 112 | 113 | 1.7.2 114 | 115 | 116 | 117 | 118 | 119 | 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 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | {B54BEC10-F397-45B0-B080-E8E0292B2667} 167 | ReactiveExtensionExamples 168 | 169 | 170 | 171 | --------------------------------------------------------------------------------