Fabrizio Giudici is a Senior Java Architect with a long Java experience in the industrial field. He runs Tidalwave, his own consultancy company, and has contributed to Java success stories in a number of fields, including Formula One. Fabrizio often appears as a speaker at international Java conferences such as JavaOne and Devoxx and is member of JUG Milano and the NetBeans Dream Team. Fabrizio is a DZone MVB and is not an employee of DZone and has posted 67 posts at DZone. You can read more from them at their website. View Full User Profile

A Cleaner MVC Inspired by Continuation-Passing Style

06.22.2011
| 19585 views |
  • submit to reddit

4. Going asynchronous with callbacks

Adopting an asynchronous message passing style is an elegant solution to the threading problem. In practice, we guarantee that getNewsFeed() completes immediately, even when the result is not ready; the caller will be notified later, by means of a callback that is passed as argument:

    public static interface AvailabilityNotifier
      {
        public void notifyFeedAvailable (@Nonnull RssFeed newsFeed);

        public void notifyCachedFeedAvailable (@Nonnull RssFeed newsFeed);
        
        public void notifyFeedUnavailable();

        public void notifyFeedCouldBeDownloaded (@Nonnull DownloadConfirmation confirmation);
      }

AvailabilityNotifier will be called back in one of its four methods for one of the four possible outcomes:

  1. notifyFeedAvailable() notifies that a fresh feed is available and carries it as a parameter;

  2. notifyCachedFeedAvailable() notifies that a cached feed is available (for some reason a fresh copy couldn't be retrieved) and carries it at as parameter;

  3. notifyFeedUnavailable() notifies that it is not possible to retrieve any data;

  4. notifyFeedCouldBeDownloaded() notifies that there's no data available, but it could be possible to download it by connecting to the network.

Of course, the presence of a specific callback method for each outcome also eliminates the need of a switch. Should a future version of NewsService allow a fifth outcome, our code won't be compilable unless we provide the implementation of a fifth method, forcing us to take care of the new behavior. Or, perhaps, it will be possible to provide a default implementation of the new method (thus turning AvailabilityNotifier into an abstract class) to have backward compatibility with existing code. We would be safe in both cases.

Now, let's focus on the fourth method, notifyFeedCouldBeDownloaded(): it's the one notifying that the data could be downloaded, upon confirmation. As you can see, it has an argument, called DownloadConfirmation. It's another callback:

    public static interface DownloadConfirmation
      {
        public void confirmDownload();
      }

The exposed single method confirmDownload() allows to complete the operation (of course, if the user cancels the operation, the method won't be called). doDownload() works again asynchronously, returning immediately (so it can be called by a UI thread); when the operation is completed, with a positive or negative outcome, the initial instance of AvailabilityNotifier will be notified again. So, instead of exposing a getNewsFeedWithoutConfirmation() into NewsService, warning that it shouldn't be called but under some circumstances, confirmDownload() is available on an object that only materializes, and thus can be called, precisely under those circumstances.

Let's think of these callbacks as objects representing a state in the the control flow of the application: each state only exposes methods doing things that are allowed to be done in that state. A great reduction of the chances of an error.

Note that the implementation of DownloadConfirmation could even enforce a check in confirmDownload() so it can be called only once per instance (and perhaps within a certain time window). It would prevent tricks such as keeping a reference to DownloadConfirmation to call confirmDownload() multiple times during other interactions. Sure, we can't automatically enforce the proper implementation in NewsViewController (after all it could just pretend the user has confirmed without asking anything), but we've made some reasonable efforts to avoid some trivial errors.

Below is a diagram of the News API, where you can see the relationships among the classes described so far:

The News API

Figure 2. The News API


We can now have a look at the implementation of the method getNewsFeed() in NewsService (I'm not showing the full listing, but methods names are self-describing):

    @Override
    public void getNewsFeed (final @Nonnull AvailabilityNotifier availabilityNotifier)
      {

        try
          {
            availabilityNotifier.notifyFeedAvailable(cache.getRssFeed());
          }
        catch (NotFoundException e)
          {
            ensureCacheIsInitialized(availabilityNotifier);
            
            if (!cache.getStatus().isDownloadNeeded())
              {
                readNewsFeedAndNotifyAvailability(availabilityNotifier);                    
              }
            else if (preferences.get().isNewsDownloadAllowed())
              {
                downloadNewsFeedAndNotifyAvailability(availabilityNotifier);
              }
            else
              {
                availabilityNotifier.notifyFeedCouldBeDownloaded(new DownloadConfirmation() 
                  {
                    public void confirmDownload() 
                      {
                        downloadNewsFeedAndNotifyAvailability(availabilityNotifier);
                      }
                  });
              }
          }
      }

AttachmentSize
it.tidalwave.bluebill.mobile.news_.png32.91 KB
it.tidalwave.bluebill.mobile.news_.ui_.png13.07 KB
package.png12.46 KB
UserNotificationWithFeedback.png21.21 KB
Published at DZone with permission of Fabrizio Giudici, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)