Thoughts, reflections, and ideas

Full control in your hybrid mobile apps with a local server, 8fit

Posted on

Since 8fit was developed we’ve been using the webview application cache to send new updates to the users. It allowed us to update without releasing new apps (something slow if you think about the Apple review process). Although it’s something good, it has also some cons, it’s a bit difficult to setup everything properly, specially the server config to ensure the webview doesn’t cache the files that shouldn’t be cached. Moreover the control over the update process is very low and it increases the boot times because sometimes it has to get the resources remotely when it doesn’t actually need them.

Is there any other way to do it if my app is an hybrid app? Yes, there is. This solution is closer to the bundling format of native apps, where you package everything inside the app and that’s “server” to the Webview. Frameworks like Ionic does it, but if you bundle the web resource you have to follow the native release cycles and again, it’s slow in case of Apple (slow iteration speed).

We were thinking if there was a solution that took the advantages of having everything locally to serve it quickly and that had the flexibility to update remotely instead of having everything bundled with the app and started working on something we called internally “Frontend Manager’.

That approach gives us a lot flexibility, especially when working quickly on new features, designs and integrations. The web app still can decide when to download the frontend web app, when to inject it into the webview, and even force high-priority updates if needed.

HTML5 Application Cache

As I mentioned we’d been relying on Application Cache but the problems with app cache have been documented plenty of places (http://alistapart.com/article/application-cache-is-a-douchebag). The main problem it has is that even after dealing with all the edge cases it’s possible to have users stuck on old versions, or experience very long “flaxes of blank white screen”. And it’s also impossible to force an update when necessary.

A lot of users have been reporting us white screens, frozen apps, and we could only tell them to clear the app data in order to clear the Application Cache and start the app with everything cleaned. It was really a frustrating user experience.

Native Frontend manager

Why not having a kind of native controller controller that decided when and how inject the content into the app? That’s what we did and we’re very happy with it. It didn’t require a lot of changes in the frontend which is great because we didn’t have to couple our implementation to a particular problem. What did we need?

We’ve also another way to reload the frontend which is using a bridge call through Javascript but we only use it for testing and point the app to different environment instead of having to rebuild the app every time.

The example below shows a config of the server to serve files and proxy some calls:

- (void)setupServerHandlers:(NSString*)frontendPath
    [self addGETHandlerForBasePath:@"/a/"
    [self addAPIHandlerForRequestMethod:@"GET"];
    [self addAPIHandlerForRequestMethod:@"POST"];
    [self addAPIHandlerForRequestMethod:@"PUT"];
    [self addAPIHandlerForRequestMethod:@"PATCH"];
    [self addAPIHandlerForRequestMethod:@"DELETE"];

#pragma mark - Custom Setters

- (void)addAPIHandlerForRequestMethod:(NSString *)requestMethod
    typeof (self) __weak welf = self;
    [self addHandlerForMethod:requestMethod
                 requestClass:[GCDWebServerDataRequest class]
            asyncProcessBlock:^(GCDWebServerRequest *request, GCDWebServerCompletionBlock completionBlock) {
                // Proxying the source request
                NSString *urlString = [welf.remoteRootUrl stringByAppendingPathComponent:request.path];
                NSMutableURLRequest *proxyRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
                [proxyRequest setHTTPMethod:requestMethod];
                proxyRequest.allHTTPHeaderFields = request.headers;
                if ([request isKindOfClass:[GCDWebServerDataRequest class]]) {
                    proxyRequest.HTTPBody = [(GCDWebServerDataRequest*)request data];
                NSURLSessionDataTask *dataTask = [welf.manager dataTaskWithRequest:proxyRequest completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
                    if (error) {
                        completionBlock([GCDWebServerResponse responseWithStatusCode:[(NSHTTPURLResponse*)response statusCode]]);
                    else {
                        NSString *contentType = [(NSHTTPURLResponse*)response allHeaderFields][@"Content-Type"];
                        completionBlock([GCDWebServerDataResponse responseWithData:responseObject contentType:contentType]);
                [dataTask resume];

As you can see in case of being an static file we can directly specify the local path where the files has to be read (we have to ensure the folders tree is the right one) and in case of the API we proxy the requests that match a regular expression and manage them using a web client, that in that case is AFNetworking. And the magic works!

Hotfixes & pushing updates

Finally and taking advantage of the silent push notifications we have on iOS that are a kind of “content notifications” and the full control we have in case of Android we thought, why not connecting it with our frontend manager in order to sync the frontend under certain conditions? Yei! We did it. When the app receives the push notification it starts the synchronization and loads the frontend only if the app was in background. That way we don’t reload the app is the user is doing a workout. Thinking about the options that we have now with that feature…:

Some gotchas

Next steps

Frontend manager was our next big feature that we’ve been waiting for months. Now we want to test it deeply, include it in our release QA cycles and cover all possible edge cases because it’s a very critical feature. Frontend Manager has supposed a breath to keep thinking in the product and building more fluid experiences. We’ll start migrating features to native, the company is getting bigger and we’re having more resources to think about a future 8fit 100% written on Java, Objective-C, Swift.


Note: If you also are working on an hybrid app and you're looking for a similar approach you can reach me at [email protected] I'll pleased to help you.

Thanks Pedro Sola for the article review and its help during the feature development