Note: Forums will be making significant UX changes to address key usability improvements surrounding search, discoverability and navigation. To learn more about these changes please visit the announcement which can be found HERE.

Answered where to use "using" when you Subscribe in Windows 8

  • Monday, January 14, 2013 1:44 AM
     
      Has Code

    Hi all -- I have gone through Jesse Liberty's book and also a whole bunch of online materials, but because I don't use RX **enough** I have undoubtedly replicated a simple error after a long break. :-(

    I have a simple WinRT/Windows 8 app that has a text box at the top and a textblock on the bottom. I want to hook the TextChanged event and given a few filters, I want to write the current result to the textblock down below. (A simple test application for better things shortly.) I do two things: First, I do this in the MainPage constructor (straight out of the standard sample):

                var input =
                (
                    from text in Observable.FromEventPattern<TextChangedEventArgs>(textBox, "TextChanged")
                    select ((TextBox)text.Sender).Text) // select only the text itself
                    .Where(text => text.Length >= 3) // when the text becomes 3 or more chars
                    .Throttle(TimeSpan.FromSeconds(.3)) // ignore any changes for .3 seconds
                    .DistinctUntilChanged() // give us only actual string changes
                    .Do(x =>
                    {
                        System.Diagnostics.Debug.WriteLine(x);
                    }// output whatever you select to the debug console.
                );

    This works fine. But I want to bind this to the textblock -- eventually I'll add a nested call to the bing search service, but for right now, I just want to replicate that structure nicely -- so I add this, but it does NOT work.

                // this does not work.
                /*
                using (input.ObserveOnDispatcher().Subscribe(words =>
                    {
                        output.Text = words;
                    }
                    
                    ))
                {
                    
                }
                */

    This, instead, DOES work:

                // This works.
                input.ObserveOnDispatcher().Subscribe(words =>
                    {
                        
                        output.Text = words;
                    }
                );

    Now, I suspect that what is happening here is that "using" means that the Unsubscribe operation occurs after this code executes, which means that the stream of inputs to the textblock is suddenly turned off. (Another way of saying this is that I think it is a "live" subscription only for as long as it takes to execute both the underlying subscription and unsubscription, and then I'm left with a stream of inputs but nothing listening to it.)

    Do I understand this correctly?

    If so, where would I subscribe and unsubscribe when creating a standard WinRT application? I need the subscription to the observable to run whilst the page is in view; I would need to handle that both when the page was loaded and unloaded, but also if it were suspending and unsuspending.... I suspect this lack of knowledge is TOTALLY related to my lack of experience with silverlight/xaml/windows 8. :-0

All Replies

  • Monday, January 14, 2013 6:41 AM
     
     Answered Has Code

    Hi,

    > Do I understand this correctly?

    Yes.

    > If so, where would I subscribe and unsubscribe when creating a standard WinRT application? 
    > I need the subscription to the observable to run whilst the page is in view; [snip]

    You can subscribe to queries when handling the Loaded event and save the disposables to fields on your page or view model.  Then dispose of the fields when handling the Unloaded event.  I use the same pattern for WPF and Silverlight applications.

    Furthermore, you can simplify the pattern by defining an iterator block to generate all subscriptions for the page and gather all of the disposables into a disposable collection.

    However, the suspend and resume behaviors of WinRT do not raise the Unloaded and Loaded events apparently, so additional code has to be added to the code generated by Visual Studio's item templates in order to achieve the desired effect.

    First, add the partial keyword to the LayoutAwarePage class.  Visual Studio defines this class for you in your project's Common folder.

    public partial class LayoutAwarePage : Page

    Also in the LayoutAwarePage class, locate its parameterless constructor and add the following event handlers, though don't specify the actual methods yet.  We'll specify them later in another partial class.

    public LayoutAwarePage()
    {
        if (Windows.ApplicationModel.DesignMode.DesignModeEnabled) return;
    
        // Add these two event handlers; note that these methods don't exist yet.
        Loaded += OnLoaded;
        Unloaded += OnUnloaded;
    
        // Existing constructor code...
    }

    Add a new code file to your project's Common folder and name it LayoutAwarePage-Subscriptions.cs.   Paste the following code:

    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Reactive.Disposables;
    using Windows.ApplicationModel;
    using Windows.UI.Xaml;
    
    namespace {YourNamespace}.Common
    {
    	public partial class LayoutAwarePage
    	{
    		private readonly SerialDisposable subscriptions = new SerialDisposable();
    
    		protected virtual IEnumerable<IDisposable> Subscribe()
    		{
    			return Enumerable.Empty<IDisposable>();
    		}
    
    		protected virtual void Unsubscribe()
    		{
    		}
    
    		private void SubscribeCore()
    		{
    			Debug.WriteLine("Creating subscriptions for page {0}.", this.GetType());
    
    			subscriptions.Disposable = new CompositeDisposable(Subscribe());
    
    			Debug.WriteLine("Created subscriptions for page {0}.", this.GetType());
    		}
    
    		private void UnsubscribeCore()
    		{
    			Debug.WriteLine("Disposing subscriptions for page {0}.", this.GetType());
    
    			Unsubscribe();
    
    			subscriptions.Disposable = Disposable.Empty;
    
    			Debug.WriteLine("Disposed subscriptions for page {0}.", this.GetType());
    		}
    
    		protected virtual void OnLoaded(object sender, RoutedEventArgs e)
    		{
    			SubscribeCore();
    		}
    
    		protected virtual void OnUnloaded(object sender, RoutedEventArgs e)
    		{
    			UnsubscribeCore();
    		}
    
    		protected internal virtual void OnResuming(object sender, object e)
    		{
    			SubscribeCore();
    		}
    
    		protected internal virtual void OnSuspending(object sender, SuspendingEventArgs e)
    		{
    			UnsubscribeCore();
    		}
    	}
    }

    Open your project's App.xaml.cs file.  Locate its parameterless constructor and add an event handler for the Resuming event.

    public App()
    {
    	this.InitializeComponent();
    	this.Resuming += OnResuming;	// Add this event handler
    	this.Suspending += OnSuspending;
    }
    
    private void OnResuming(object sender, object e)
    {
    	var page = CurrentPage;
    
    	if (page != null)
    	{
    		page.OnResuming(sender, e);
    	}
    }
    
    private LayoutAwarePage CurrentPage
    {
    	get
    	{
    		var rootFrame = Window.Current.Content as Frame;
    
    		return rootFrame == null ? null : rootFrame.Content as LayoutAwarePage;
    	}
    }

    Finally, locate the existing OnSuspending method and insert the following code.

    private async void OnSuspending(object sender, SuspendingEventArgs e)
    {
    	var page = CurrentPage;
    
    	if (page != null)
    	{
    		page.OnSuspending(sender, e);
    	}
    
    	// Existing code goes here...
    }

    Now you can simply override your page's Subscribe method to create your subscriptions.  For example:

    protected override IEnumerable<IDisposable> Subscribe()
    {
    	var textBoxChanged = Observable.FromEventPattern<TextChangedEventArgs>(textBox, "TextChanged");
    
    	yield return
    		(from change in textBoxChanged
    			let text = ((TextBox) change.Sender).Text
    			where text.Length >= 3
    			select text)
    			.Throttle(TimeSpan.FromSeconds(.3))
    			.DistinctUntilChanged()
    			.Do(text => Debug.WriteLine(text))
    			.ObserveOnDispatcher()
    			.Subscribe(text => output.Text = text);
    }

    - Dave


    http://davesexton.com/blog

  • Monday, January 14, 2013 10:20 AM
     
      Has Code

    w.r.t  the last part of your post (the using(...) block); yes I think what you are trying to say is correct.

    This code

    using (input.ObserveOnDispatcher().Subscribe(words =>
      {
        output.Text = words;
      }
      ))
    {
    }


    ...translates to this:

    var subscription = input.ObserveOnDispatcher().Subscribe(words =>
      {
        output.Text = words;
      }));
    subscription.Dispose();

    You are creating a subscription, and as fast as you can are disposing it again :-(

    I think you want to stash that subscription (the IDisposable) and dispose it either when the app/view is unloaded or suspended (what ever makes sense), see Dave's full explanation answer.


    Lee Campbell http://LeeCampbell.blogspot.com

  • Monday, January 14, 2013 6:19 PM
     
     

    Note:

    I just fixed a bug in my previous code example.  I changed MultipleAssignmentDisposable to SerialDisposable so that the subscriptions are actually disposed!

    - Dave


    http://davesexton.com/blog