Creating Aggregate Projects on Theos: A Configurable Tweak.

More often than not, you will want to create a Theos project that is composed of one or various subprojects. A very common case for this would be to write a Tweak that can be configured by the user (guess what we’re writing today…): You’d create a Tweak project that contains a PreferencesLoader subproject that is in charge of writing the tweak’s behaviour. Aggregrate projects with Theos are very easy to build.

In this tutorial we’re going to write an Aggregate project that does just that: We will write a very simple tweak with a simple PreferencesLoader that will decide whether the tweak should alert the user when the app has been launched or not.

You have seen this simple tweak in action before: I wrote it in my Writing a MobileSubstrate Tweak for iOS tutorial. In fact to save us the time, I will assume you followed that tutorial, and that you managed to compile it and run it. We will write the same tweak with a twist. In that way we will save us the trouble of writing a MobileSubstrate tweak and get straight to the point of this tutorial.

In this tutorial…

  • You’ll learn the basics of a PreferencesLoader project (Note that this is NOT the same a Settings.bundle found in App Store apps, but they can share similarities).
  • You will learn to write aggregate projects with Theos. An aggregate project is a project that contains one or more sub projects.

The following assumptions are made:

  • You know how to write iOS apps. You know the SDK and you have written an app. It doesn’t matter if said app has been published. You have the necessary skills to write an iOS app.
  • You know how a Settings.bundle works and its limitations for iOS Apps. You also know how to make bundles and edit their plists manually.
  • You know how to write Mobile Substrate tweaks. If you don’t, head over to my tutorial to get started…

Alright, without further ado, let’s get started!

Writing The Mobile Substrate Tweak: Aggregate Projects Don’t Come Alone!

Since we’re going to build this project on top of an old tweak from an old tutorial, go build the tweak described in the tutorial linked above. It shouldn’t take you long (it’s literally less than 15 lines long), so go ahead and build it. I will wait for you.

Got it up and running? Cool, let’s continue.

Discussion on PreferencesLoader: The Most Common Use For Aggregate Projects

PreferencesLoader was written by DHowett as a way to let developers create settings pages for their apps and tweaks in the Settings app. PreferencesLoader can be very different than the standard Settings.bundle you’re used to.

When it comes to PreferencesLoader, there are two approaches to fetch to create a settings page for your app: The simple approach and the PreferencesLoader approach.

The Simple Approach

The simple approach is very similar to a Settings.bundle in that the cells are loaded from a plist file stored in /Library/PreferenceLoader/Preferences/. This plist contains the specifiers that your app requires. These can be simple cells that contain switches, text, text fields, and so on. The specifiers are loaded from their respective files and using the simple approach your bundle doesn’t execute code. It just loads and saves the values in the modifiable fields of the bundle.

The PreferenceBundle Approach.

Unlike the static settings the Simple Approach offers, this approach actually allows your settings bundle to execute code. The entry plist for this approach must have the same name of the PreferenceBundle to run and define it’s isController option to true. You can show your own view and everything you need. I will cover the PreferenceBundle Approach in another tutorial.

For more information on PreferenceLoader, refer to the iPhoneDevWiki page on the topic.

Creating Aggregate Projects: Getting Our Hands Dirty.

Now you have the tweak that alerts the user when an app has been launched. This is cool, very cool, but what if we wanted the user to have the ability to turn it off and on as he pleases? We need to write a settings page for it. But it wouldn’t make sense to write a different project for it. Does it make sense to ask the user to install the tweak, and then separately install it’s settings page? I don’t think so.

Aggregate projects come to the rescue. We’re going to create the settings project for our tweak, and they will be installed at the same time, since the settings project will be a subproject of the main project! But how do we do that? There is no template or anything to simplify the job of creating an aggregate project.

You don’t really need to do much to create an aggregate project. In fact, it’s just a matter of adding two extra lines to your makefile and that’s it.

But we don’t have the preferences project set up yet. In your terminal window, cd to your current project’s directory and run Theos’ nic.pl to create a new project. Select “iphone/preference_bundle” and finish the wizard.

If you’re lucky, Theos will automatically detect you created a project inside another one, and it will modify the makefile for now. If it doesn’t, don’t worry. Simply add the following two lines to it and you will be fine:

SUBPROJECTS += notifiersettings
include $(THEOS_MAKE_PATH)/aggregate.mk

It’s important the SUBPROJECTS specifier is before the include of the aggregate makefile.

Wiring Things Up: The Core Of Aggregate Projects.

make package install your project now. Open the settings app. Apart from our nice alert that tells the user Settings has been launched, if you scroll down a bit you should see a cell containing our settings preferences name for our tweak. Open it up and you should see this:

If you change the switch and go back, you will see its current state was automatically saved by our good friend PreferenceLoader. At least with saving settings, you don’t have to worry about that if you use the simple approach.

Naturally now we gotta modify its strings so it makes sense to the user.

Go to the PreferencesLoader subproject. Now open its Resources folder and open the plist there (NOT the one called Info.plist – the other one that has then name of your project). Since you know how to modify these, just change the name of the label to something like “Alert user on app launch”:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>items</key>
	<array>
		<dict>
			<key>cell</key>
			<string>PSGroupCell</string>
			<key>label</key>
			<string>Notifier Settings.</string>
		</dict>
		<dict>
			<key>cell</key>
			<string>PSSwitchCell</string>
			<key>default</key>
			<true/>
			<key>defaults</key>
			<string>com.AndyIbanez.NotifierSettings</string>
			<key>key</key>
			<string>alertLaunch</string>
			<key>label</key>
			<string>Alert User on App Launch</string>
		</dict>
	</array>
	<key>title</key>
	<string>NotifierSettings</string>
</dict>
</plist>

Now we just have to modify the actual tweak to read that value and alert the user if he so wishes (AKA if alertLaunch is YES).

With jailbreak apps you don’t have many “commodities” as you do with App Store apps, so you can’t use the same methods you’d use in App Store apps to read settings from a plist.

Instead, we have to read the plist manually, fetch the values we have to check for manually, and do whatever we want with those values.

You should know this by now, but in case you don’t remember, just create an NSDictionary object and use initWithContentsOfFile or dictionaryWithContentsOfFile. The full path to your settings plist is /var/mobile/Library/Preferences/YOUR_PLIST.plist.

In other words, your reading code would look like this:

	NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithContentsOfFile:
									[NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), @"com.AndyIbanez.NotifierSettings.plist"]];

(Note the use of NSHomeDirectory(). The iPhoneDev Wiki which is maintained by the guys behind our awesome jailbreak development tools recommend we never refer to /var/mobile directly, and that we should use NSHomeDirectory() instead.)

With that in mind, then you just read the value you want (alertLaunch in my case and probably yours too), and use it accordingly. When done, your whole method should look like this:

-(void)launch
{
    NSString *appName = [self displayName];
    
	NSMutableDictionary *settings = [NSMutableDictionary dictionaryWithContentsOfFile:
									[NSString stringWithFormat:@"%@/Library/Preferences/%@", NSHomeDirectory(), @"com.AndyIbanez.NotifierSettings.plist"]];
	NSNumber* shouldNotify = [settings objectForKey:@"alertLaunch"];
	
	if([shouldNotify boolValue] == YES)
	{
	    NSString *message = [NSString stringWithFormat:@"The app %@ has been launched", appName, nil];
	    UIAlertView *alert1 = [[UIAlertView alloc] initWithTitle:appName message:message delegate:self cancelButtonTitle:@"OK" otherButtonTitles:nil];
	    [alert1 show];
	    [alert1 release];
    }
    %orig;
}

And you’re done! Try it now. When the switch is on, you’ll be alerted when an app has launched. When it’s off, it will do the opposite.

You can downloade the source code for the project in my Github here. Study it and improve it, break it, etc..

If you have any questions or comments, you can as usual use the comments section below.

CHANGELOG:
_____________
May 10, 2013 at 6:00 PM [America/La_Paz]: First version of this post.
May 12, 2012 at 2:00 PM [America/La_Paz]: I forgot to add the source code for the settings plist. It’s added now.

Positive SSL