Sean Farrell - Big Nerd Ranch Tue, 19 Oct 2021 17:46:09 +0000 en-US hourly 1 https://wordpress.org/?v=6.5.5 Choosing the Right Background Scheduler in Android https://bignerdranch.com/blog/choosing-the-right-background-scheduler-in-android/ https://bignerdranch.com/blog/choosing-the-right-background-scheduler-in-android/#respond Thu, 05 May 2016 09:00:53 +0000 https://nerdranchighq.wpengine.com/blog/choosing-the-right-background-scheduler-in-android/ Let's take a look at the different methods for scheduling tasks in Android and discuss the pros and cons of each so that you can determine when to use each tool *before* implementing an inappropriate one.

The post Choosing the Right Background Scheduler in Android appeared first on Big Nerd Ranch.

]]>

Over the last few years, Google has introduced several new classes to Android for scheduling work that needs to run outside the scope of an application’s lifecycle. Many of these new schedulers implement features that improve battery life, as do updates to older methods. And as of Android Marshmallow, these schedulers became even more efficient thanks to Doze Mode, which doesn’t even require any effort from developers!

Let’s take a look at the different methods for scheduling tasks in Android and discuss the pros and cons of each so that you can determine when to use each tool—before implementing an inappropriate one.

Scheduling Work Within Your Application Process

First things first. If the work that you need to schedule does not need to live beyond the scope of your application, Google recommends using the Handler class along with a Timer and Thread. For any timing operations that occur only during the lifetime of your application, it is best to use application resources rather than system resources to schedule tasks. Using a Handler is easier and more efficient than the following methods when it comes to scheduling tasks within your application process.

Using the AlarmManager to Schedule Tasks at the System Level

The AlarmManager provides access to system-level alarm services. Using the AlarmManager allows an application to schedule tasks that may need to run or repeat beyond the scope of its lifecycle. This allows the application to perform some function even after the application process or all of its Android components have been cleaned up by the system.

Typically, the AlarmManager is used to fire off a PendingIntent that will start up a Service in the future. The AlarmManager triggers Services based on an elapsed interval or at a specific clock time. Both of these options also have the ability to wake up the device when it is asleep if the alarm is urgent.

The benefits of the AlarmManager come into play when using inexact intervals or times to fire off Services. The Android system tries to batch alarms with similar intervals or times together in order to preserve battery life. By batching alarms from multiple applications, the system can avoid frequent device wake and networking.

One concern to consider while using the AlarmManager is that alarms are wiped out during device reboots. Applications need to register the RECEIVE_BOOT_COMPLETE permission in their Android Manifest and reschedule their alarms in a BroadcastReceiver.

Another concern is that a poorly designed alarm could cause battery drain.
While the AlarmManager does have the ability to wake devices and set an exact time for an alarm, the documentation mentions that developers should be wary of these features when performing networking. Aside from draining a device’s battery by avoiding batch alarms, setting an exact time for an application to sync with a server could put high strain on a server if every application installation tries to sync with the server around the same time! This can be avoided by adding some randomness to alarm intervals or times.

AlarmManager is a great candidate for scheduling if an application needs to perform a local event at an exact time or inexact interval. Alarm clock or reminder applications are great examples for AlarmManager usage. However, the documentation discourages using AlarmManager for scheduling network-related tasks. Let’s take a look at some better options for networking.

Job Scheduler

JobScheduler helps perform background work in an efficient way, especially networking. JobServices are scheduled to run based on criteria declared in JobInfo.Builder(). These criteria include performing the JobService only when the device is charging, idle, connected to a network or connected to an unmetered network. JobInfo can also include minimum delays and certain deadlines for performing the JobService. Jobs will queue up in the system to be performed at a later time if none of these criteria are met. The system will also try to batch these jobs together in the same manner that alarms are scheduled in order to save battery life when making a network connection.

Developers might be concerned about a scheduler that frequently delays firing off its JobServices. If jobs are frequently delayed and data stale as a result, it would be nice to know about such things. JobScheduler will return information about the JobService such as if it was rescheduled or failed. JobScheduler has back-off and retry logic for handling these scenarios, or developers could handle those scenarios themselves.

Subclassing JobService requires an override of its onStartJob(JobParams params) and onStopJob(JobParams params) methods. onStartJob() is where callback logic for jobs should be placed, and it runs on the main thread. Developers are responsible for threading when dealing with long running jobs. Return true to onStartJob() if separate thread processing needs to occur, or false if processing can occur on the main thread and there is no more work to be done for this job. Developers must also call jobFinished(JobParameters params, boolean needsReschedule) when the job is complete and determine whether or not to reschedule more jobs. onStopJob() will get called to stop or cleanup tasks when initial JobInfo parameters are no longer met, such as a user unplugging their device if that parameter is a requirement.

There might be a lot to think about when implementing a JobService, but it comes with a lot more flexibility than AlarmManager. Another handy feature is that scheduled jobs persist through system reboots.

There is at least one drawback to using JobScheduler. As of the writing of this post, it’s compatable only with API level 21 and higher. Here you can find the distribution of Android devices running various API levels. While there is technically no backport of JobScheduler, a similar tool is GCM Network Manager.

GCM Network Manager

GCM (Google Cloud Messaging) Network Manager actually uses Job Scheduler behind the scenes for API level 21 and higher, and features support for previous versions of Android with Google Play Services. GCMNetworkManager has all of the nice battery saving schedule features from JobScheduler, but the backwards compatibility and simplicity of its Service counterpart will make it more appealing to most developers.

Tasks are created using OneoffTask.Builder() or PeriodicTask.Builder(). The Builder pattern makes it easy to append scheduling criteria to a task. The criteria are almost identical to those from JobInfo, and the system aims to optimize battery life through batching Tasks scheduled with GcmNetworkManager. Criteria can include required network or charging states and desired windows, but the ability to create a Task as one-off or periodic takes a bit of work out of the rescheduling required in JobService. Tasks are scheduled by calling the schedule(Task task) method on an instance of GcmNetworkManager.

A subclass of GcmTaskService is required to receive scheduled Tasks. GcmTaskService requires an override of its onRunTask(TaskParams params) method, and this is where any work should be performed. onRunTask() is run on a background thread, a handy improvement over JobService's onStartJob() method.

One thing to note about GcmNetworkManager compared to JobScheduler is that application and Google Play Service updates will wipe out scheduled Services. The onInitializeTask() method of GcmTaskService can be overriden to be notified of this event and respond accordingly to reschedule the Service.

You should also be aware that GcmNetworkManager and JobScheduler were not built to perform tasks immediately or at a specific time. They are meant for performing repeated or one-off, non-imminent work while keeping battery life in mind. An alarm clock or reminder app that needed any sort of precision in timing should not use either option. This is especially true after the introduction of Doze Mode in Android Marshmallow, which we will discuss shortly.

Matt Compton wrote a great post on GCM Network Manager that goes into more detail. Check it out!

Sync Adapter

Sync Adapters were introduced with the intent of keeping Android devices and servers in sync. Syncs could be triggered from data changes in either the server or device, or by elapsed time and time of day. The system will try to batch outgoing syncs to preserve battery life, and transfers that are unable to run will queue up for transfer at a later time. The system will attempt syncs only when the device is connected to a network.

These concepts should sound pretty familiar by now. A nice optional feature included with Sync Adapters is the ability for users to see information about syncs and the ability to turn off syncing all together.

Sync adapter syncing

In order to use a Sync Adapter, applications need an authenticator and a Content Provider. The expectation is that the application will have an Account and a database if syncing is needed between a device and server. This may not be true for this relationship all of the time, but Sync Adapters do require a subclass of AbstractAccountAuthenticator and ContentProvider in order to function. Applications that do not need these classes but still wish to use Sync Adapters can simply stub out a dummy authenticator and Content Provider. The stub classes are actually the bases for Google’s handy Sync Adapter tutorial. Users can view Accounts in settings.

Sync adapter accounts

The SyncAdapter itself is declared in an XML file. Here you would specify the Account and ContentProvider required as well as whether or not the user can view the Sync Adapter in Settings, if uploading is supported, if parallel syncs are allowed, and if the Sync Adapter is always syncable.

Syncs can occur automatically when changes occur locally if you create a ContentObserver subclass and register an observer for the Content Provider. When server data changes and you receive a notification through something such as a Broadcast Receiver, you can tell your local data to sync with the server by calling ContentResolver.requestSync(). Syncs can also be set to setSyncAutomatically() in order to sync whenever a network connection is already open, thereby frequently having up-to-date data, but never draining the battery by starting up the radio when it is not already running. Content Resolvers could have periodic syncs added to them with addPeriodicSync(), specifying an interval similar to those of AlarmManager. On demand syncs can occur by calling requestSync() on a Content Provider and passing in a flag specifying immediate sync or a more efficient sync that may wait a few seconds to batch other syncs. On demand syncing is discouraged due to battery drain, but is a common pattern seen on lists that are pulled downward to refresh.

Sync Adapters often require quite a bit more work to set up than the previous methods we discussed. However, if an application already supports Android Users and uses Content Providers, a lot of the boilerplate work is already done. Two huge upsides to Sync Adapter (compared with Job Scheduler or GCM Network Manager) are that it supports API level 7 and higher and does not require Google Play Services. Sync Adapters can leave you with the largest number of supported devices.

Doze Mode

Doze Mode was introduced in Android Marshmallow as a way to minimize battery drain while a user has been away from their device for a period of time. Doze Mode is automatically enabled on devices running Android API level 23 and higher. It is not a scheduler that developers need to implement. Rather, it affects the other schedulers we have already discussed.

Doze Mode is enabled after a period of time under these conditions: The user’s screen has been off, the device has been stationary and the device has not been charging. When in Doze Mode, network access is suspended, the system ignores wake locks and standard alarms are deferred, including the running of Sync Adapters and JobSchedulers. There are maintenance windows that infrequently occur to allow all of these schedulers within the system to run at once, therefore preserving battery through infrequent and grouped network calls.

Doze Mode's effect on battery life

Notice that maintenance windows occur less frequently the longer the device is dozing. Once the user moves the device, turns on the screen or connects to a charger, normal activity resumes. You could imagine the battery benefits while users sleep or leave their phone unattended for a long period of time.

There are several scenarios developers need to concern themselves with following the introduction of Doze Mode. Alarms that need to fire off at an exact time can still do so with the AlarmManager using its setAndAllowWhileIdle() or setExactAndAllowWhileIdle() methods. Imagine an alarm clock app that did not wake its user in the morning due to Doze Mode! Another scenario would involve important incoming notifications, such as texts, emails or even tweets, if that is what users find important. For those cases, developers will need to use GCM with high-priority messages.

Notice the “priority” : “high” attribute-value pair below.

{
  "to" : "bk3RNwTe3H0:CI2k_HHwgIpoDKCIZvvDMExUdFQ3P1...",
  "priority" : "high",
  "notification" : {
    "body" : "Apparently this user thinks Tweets are important enough to disturb slumber!",
    "title" : "This cat's hair looks like Donald Trump's hair.",
    "icon" : "cat",
  },
  "data" : {
    "contents" : "https://twitter.com/trumpyourcat"
  }
}

Applications with core functionality requiring work to schedule outside of the Doze Mode maintenance windows can be whitelisted. Such whitelisting requires a special permission, and Google Play policies allow it only in certain cases.

These are currently the only scenarios in which developers can interrupt Doze Mode outside of the maintenance window. This isn’t usually a problem, since most scheduling should involve efficient, infrequent scheduling, but it is something to be aware of.

Summary

There are plenty of things to consider when trying to choose the right scheduler for Android applications:

  • Will the scheduler need to exist outside the scope of the application lifecycle?
  • Will the app need to schedule networking?
  • Which scheduler will allow the support of the most users?

Let’s summarize the main pros and cons of the different methods we dove into:

  • When using the AlarmManager, keep alarm frequency and device waking to a minimum, add randomness to the alarm’s timing when performing network requests so as to not overload a server, avoid clock time and precise timing unless the application needs to notify the user of something at a specific time, and consider another scheduler for networking.
  • JobScheduler provides efficient background scheduling, but only if your user is running API level 21 or higher (that’s about 25% of users at the time of writing this post).
  • GCM Network Manager provides efficient background scheduling—if your user has Google Play Services installed (this excludes Amazon devices). GCMTaskService is typically simpler to manage and more difficult to mess up than JobService.
  • Sync Adapters are a great solution to sync local data with server data, especially if you already have User authentication and use Content Providers in your application. If you need to support devices running earlier than Android API level 21 in addition to devices without Google Play Services, Sync Adapter may be your best option.
  • Doze Mode is wonderful in that developers do not need to do a thing to implement this system battery efficiency. AlarmManager and GCM Network Manager are the only options for interrupting a Doze.

Hopefully this post provides a good starting point in choosing the right path for background work on Android. With Android N already out for preview, and a planned release expected later in 2016, stay tuned for any updates about these topics. The documentation for behavioral changes mentions an update to Doze Mode and suggests JobScheduler as an option for handling new background optimizations.

The post Choosing the Right Background Scheduler in Android appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/choosing-the-right-background-scheduler-in-android/feed/ 0
Understanding Android’s LayoutInflater.inflate() https://bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/ https://bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/#respond Tue, 16 Feb 2016 09:53:37 +0000 https://nerdranchighq.wpengine.com/blog/understanding-androids-layoutinflater-inflate/ It's easy to get comfortable with boilerplate setup code, so much so that we gloss over the finer details. I've experienced this with [LayoutInflater](http://developer.android.com/reference/android/view/LayoutInflater.html) (which coverts an XML layout file into corresponding ViewGroups and Widgets) and the way it inflates Views inside Fragment's onCreateView() method. Upon looking for clarification in Google documentation and discussion on the rest of the web, I noticed that many others were not only unsure of the specifics of LayoutInflater's `inflate()` method, but were completely misusing it.

The post Understanding Android’s LayoutInflater.inflate() appeared first on Big Nerd Ranch.

]]>

It’s easy to get comfortable with boilerplate setup code, so much so that we gloss over the finer details. I’ve experienced this with LayoutInflater (which coverts an XML layout file into corresponding ViewGroups and Widgets) and the way it inflates Views inside Fragment’s onCreateView() method. Upon looking for clarification in Google documentation and discussion on the rest of the web, I noticed that many others were not only unsure of the specifics of LayoutInflater’s inflate() method, but were completely misusing it.

Much of the confusion may come from Google’s vague documentation in regards to attachToRoot, the optional third parameter of the inflate() method.

Whether the inflated hierarchy should be attached to the root parameter? If false, root is only used to create the correct subclass of LayoutParams for the root view of the XML.

Maybe the confusion comes from a statement that ends in a question mark?

Confusion

The general gist is this: If attachToRoot is set to true, then the layout file specified in the first parameter is inflated and attached to the ViewGroup specified in the second parameter.

Then the method returns this combined View, with the ViewGroup as the root. When attachToRoot is false, the layout file from the first parameter is inflated and returned as a View. The root of the returned View would simply be the root specified in the layout file. In either case, the ViewGroup’s LayoutParams are needed to correctly size and position the View created from the layout file.

Passing in true for attachToRoot results in a layout file’s inflated View being added to the ViewGroup right on the spot. Passing in false for attachToRoot means that the View created from the layout file will get added to the ViewGroup in some other way.

Let’s break down both scenarios with plenty of examples so we can better understand.

attachToRoot Set to True

Imagine we specified a button in an XML layout file with its layout width and layout height set to match_parent.

<Button xmlns:android="http://schemas.android.com/apk/res/android"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:id="@+id/custom_button">
</Button>

We now want to programmatically add this Button to a LinearLayout inside of a Fragment or Activity. If our LinearLayout is already a member variable, mLinearLayout, we can simply add the button with the following:

inflater.inflate(R.layout.custom_button, mLinearLayout, true);

We specified that we want to inflate the Button from its layout resource file; we then tell the LayoutInflater that we want to attach it to mLinearLayout. Our layout parameters are honored because we know the Button gets added to a LinearLayout. The Button’s layout params type should be LinearLayout.LayoutParams.

The following would also be equivalent. LayoutInflater’s two parameter inflate() method automatically sets attachToRoot to true for us.

inflater.inflate(R.layout.custom_button, mLinearLayout);

Another appropriate use of passing true for attachToRoot is a custom View. Let’s look at an example where a layout file uses a <merge> tag for its root. Using a <merge> tag signifies that the layout file allows for flexibility in terms of the type of root ViewGroup it may have.

public class MyCustomView extends LinearLayout {
    ...
    private void init() {
        LayoutInflater inflater = LayoutInflater.from(getContext());
        inflater.inflate(R.layout.view_with_merge_tag, this);
    }
}

This is a perfect use for a true attachToRoot parameter. The layout file does not have a root ViewGroup in this example, so we specify our custom LinearLayout to be its root. If our layout file had a FrameLayout as its root instead of <merge>, the FrameLayout and its children would inflate as normal. Then the FrameLayout and children would get added to the LinearLayout, leaving the LinearLayout as the root ViewGroup containing the FrameLayout and children.

Download our free eBook to learn why professional development opportunities are key to your company’s success.

attachToRoot Set to False

Let’s take a look at when you would want to set attachToRoot to false. In this scenario, the View specified in the first parameter of inflate() is not attached to the ViewGroup in the second parameter at this point in time.

Recall our Button example from earlier, where we want to attach a custom Button from a layout file to mLinearLayout. We can still attach our Button to mLinearLayout by passing in false for attachToRoot—we just manually add it ourselves afterward.

Button button = (Button) inflater.inflate(R.layout.custom_button, mLinearLayout, false);
mLinearLayout.addView(button);

These two lines of code are equivalent to what we wrote earlier in one line of code when we passed in true for attachToRoot. By passing in false, we say that we do not want to attach our View to the root ViewGroup just yet. We are saying that it will happen at some other point in time. In this example, the other point in time is simply the addView() method used immediately below inflation.

The false attachToRoot example requires a bit more work when we manually add the View to a ViewGroup. Adding our Button to our LinearLayout was more convenient with one line of code when attachToRoot was true. Let’s look at some scenarios that absolutely require attachToRoot to be false.

A RecyclerView’s children should be inflated with attachToRoot passed in as false. The child views are inflated in onCreateViewHolder().

public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    LayoutInflater inflater = LayoutInflater.from(getActivity());
    View view = inflater.inflate(android.R.layout.list_item_recyclerView, parent, false);
    return new ViewHolder(view);
}

RecyclerViews, not us, are responsible for determining when to inflate and present its child Views. The attachToRoot parameter should be false anytime we are not responsible for adding a View to a ViewGroup.

When inflating and returning a Fragment’s View in onCreateView(), be sure to pass in false for attachToRoot. If you pass in true, you will get an IllegalStateException because the specified child already has a parent. You should have specified where your Fragment’s view will be placed back in your Activity. It is the FragmentManager’s job to add, remove and replace Fragments.

FragmentManager fragmentManager = getSupportFragmentManager();
Fragment fragment = fragmentManager.findFragmentById(R.id.root_viewGroup);

if (fragment == null) {
    fragment = new MainFragment();
    fragmentManager.beginTransaction()
        .add(R.id.root_viewGroup, fragment)
        .commit();
}

The root_viewGroup container that will hold your Fragment in your Activity is the ViewGroup parameter given to you in onCreateView() in your Fragment. It’s also the ViewGroup you pass into LayoutInflater.inflate(). The FragmentManager will handle attaching your Fragment’s View to this ViewGroup, however. You do not want to attach it twice. Set attachToRoot to false.

public View onCreateView(LayoutInflater inflater, ViewGroup parentViewGroup, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.fragment_layout, parentViewGroup, false);
    …
    return view;
}

Why are we given our Fragment’s parent ViewGroup in the first place if we don’t want to attach it in onCreateView()? Why does the inflate() method request a root ViewGroup?

It turns out that even when we are not immediately adding our newly inflated View to its parent ViewGroup, we should still use the parent’s LayoutParams in order for the new View to determine its size and position whenever it is eventually attached.

You are bound to run into some poor advice about LayoutInflater on the web. Some people will advise you to pass in null for the root ViewGroup if you are going to pass in false for attachToRoot. However, if the parent is available, you should pass it in.

FrameLayout Root

Lint will now warn you not to pass in null for root. Your app won’t crash in this scenario, but it can misbehave. When your child View doesn’t know the correct LayoutParams for its root ViewGroup, it will try to determine them on its own using generateDefaultLayoutParams.

These default LayoutParams might not be what you desired. The LayoutParams that you specified in XML will get ignored. We might have specified that our child View should match the width of our parent View, but ended up with our parent View wrapping its own content and ending up much smaller than we expected.

There are a few scenarios in which you will not have a root ViewGroup to pass into inflate(). When creating a custom View for an AlertDialog, you do not yet have access to its parent.

AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(mContext);
View customView = inflater.inflate(R.layout.custom_alert_dialog, null);
...
dialogBuilder.setView(customView);
dialogBuilder.show();

In this case, it is okay to pass in null for the root ViewGroup. It turns out that the AlertDialog would override any LayoutParams to match_parent anyway. However, the general rule of thumb is to pass in the parent if you’re able to do so.

Avoiding Crashes, Misbehaviors and Misunderstandings

Hopefully this post helps you avoid crashes, misbehaviors and misunderstandings when using LayoutInflater. Here are some key takeaways for different uses in certain circumstances:

  • If you have a parent to pass into the root ViewGroup parameter, do so.
  • Try to avoid passing in null for the root ViewGroup.
  • Pass in false for the attachToRoot parameter when we are not the ones responsible for attaching our layout file’s View to its root ViewGroup.
  • Do not pass in true for a View that has already been attached to a ViewGroup.
  • Custom Views are a good use case to pass in true for attachToRoot.

The post Understanding Android’s LayoutInflater.inflate() appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/understanding-androids-layoutinflater-inflate/feed/ 0
WatchKit Extensions: Communicating with your Parent Application https://bignerdranch.com/blog/watchkit-extensions-communicating-with-your-parent-application/ https://bignerdranch.com/blog/watchkit-extensions-communicating-with-your-parent-application/#respond Thu, 29 Jan 2015 11:00:53 +0000 https://nerdranchighq.wpengine.com/blog/watchkit-extensions-communicating-with-your-parent-application/

The release date for the long-awaited Apple Watch has not yet been announced, but the nerd herd here is hard at work learning how to program with WatchKit. We believe that the 4th beta of Xcode 6.2 is a rather stable version of what we should expect for the public release of iOS 8.2 and WatchKit early this year.

The post WatchKit Extensions: Communicating with your Parent Application appeared first on Big Nerd Ranch.

]]>

The release date for the long-awaited Apple Watch has not yet been announced, but the nerd herd here is hard at work learning how to program with WatchKit. We believe that the 4th beta of Xcode 6.2 is a rather stable version of what we should expect for the public release of iOS 8.2 and WatchKit early this year.

With the first beta release of WatchKit in November of last year, developers noticed concurrency issues between WatchKit Extensions and their hosting iOS counterparts, mainly due to a lack of ability to communicate between the two separate processes during runtime. And what if the hosting application was not running at all on the host device? This is a very likely scenario if users were to play around with their Apple Watch while their iPhone was asleep in their pocket. These issues presented serious challenges to providing a seamless user experience.

Previously-available solutions included:

  • duplicating functionalities from your iOS app into your WatchKit Extension
  • creating a Framework for both targets to use (Apple’s recommended solution)
  • creating your own form of messaging between the WatchKit Extension and hosting iOS application, which many developers have tried

With the release of Xcode 6.2 Beta 2, Apple added a method that would allow a WatchKit Extension to communicate back and forth with its host app. The new method, -openParentApplication:reply:, allows the WatchKit app to wake its parent app and send it an NSDictionary.

The host app handles this request by implementing a new method in the app delegate, -application:handleWatchKitExtensionRequest:reply:. Here, you have the option of sending a response NSDictionary back to the WatchKit Extension.

This request-reply mechanism solves the problems we discussed earlier by allowing for simple communication between the WatchKit Extension and the hosting application.

Using -openParentApplication:reply:

Let’s take a look at a simple example of the power and benefits of these new methods. Here, we have an Apple Watch app that allows the user to select one of three words which rhyme with “at”: cat, hat and my coworker, Mat. The selected word will appear as an image in a WKInterfaceImage (the WatchKit equivalent of a UIImageView). You can download this sample project here.

Main WatchKit App

Of course, if we are adding this WatchKit Extension to a fully fledged iOS app that already has image fetching methods, why rewrite those in our WatchKit Extension? What we can do is use our handy new -openParentApplication:reply: method and pass in our desired image title in the NSDictionary. Each of our buttons will pass its title as a string into our method below:

func getDataFromParentApp(image: String) {
	let dictionary = ["Desired Word":image]
	WKInterfaceController.openParentApplication(dictionary) {
		(replyInfo, error) -> Void in
		...
	}

openParentApplication, as with any other communication between the Apple Watch and iPhone, will occur over bluetooth magic, without requiring you to do anything. Over in our AppDelegate of our iOS target, we will need to implement the other side of our method:

func application(application: UIApplication!,
	handleWatchKitExtensionRequest userInfo: [NSObject : AnyObject]!,
	reply: (([NSObject : AnyObject]!) -> Void)!) {
		getImageForWordThatRhymesWithDat(userInfo, reply)
}

We already have a method in our iOS application called getImageForWordThatRhymesWithDat, which expects an NSDictionary with the key “Desired Word” and a value containing the name of the image we desire. The method returns an NSDictionary with the key “Returned NSData” and the NSData of our image.

We will leave out the details of our getImageForWordThatRhymesWithDat method and let you imagine it is a method you have already built in one of your current iOS applications. Maybe you want to pass back a different set of data based on what your application already implements. Just make sure the data can be held in NSDictionary and transfers correctly through our two methods.

Now that our NSData is getting sent back to the WatchKit Extension, let’s look back at the completion block of our earlier openParentApplication method.

(replyInfo, error) -> Void in
	switch (replyInfo?["Returned NSData"] as? NSData, error) {
		case let (data, nil) where data != nil:
                	let image = UIImage(data: data!)
                	self.imageView.setImage(image)
		case let (_, .Some(error)):
			println("got an error: (error)") // take corrective action here
		default:
			println("no error but didn't get data either...") // unexpected situation
	}

We have now received our image NSData (or possibly an error), and we can act accordingly and set our WKInterfaceImage.

Dog WatchKit App

Our new openParentApplication method solves several problems we discussed earlier:

  • -openParentApplication:reply: allows us to briefly access our parent app when it is asleep.
  • -handleWatchKitExtensionRequest:userInfo:reply: can send data from our parent application back to our WatchKit Extension, lessening the need for code duplication or Frameworks.
  • We are adhering to Apple’s desire for the WatchKit Extension to represent another view for your iOS application, leaving much of the model and controller logic in the main iOS application.

Thanks for the new method, Apple! We are glad that they realized the issue early on and listened to developer demand. We hope this explanation helps with your early WatchKit wisdom.

The post WatchKit Extensions: Communicating with your Parent Application appeared first on Big Nerd Ranch.

]]>
https://bignerdranch.com/blog/watchkit-extensions-communicating-with-your-parent-application/feed/ 0