Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile / iOS

Cross Platform App Development using QT: State Transition on iOS Device

4.31/5 (6 votes)
27 Jun 2016CPOL6 min read 17.6K   21  
This article is more on handling the state transition of an app in iOS device

Introduction

Qt is known to develop application for multiple platforms, i.e. Windows, iOS, Android and Linux. It’s very convenient to write code once and build it for a different target. Earlier, I had an impression that application which is using network communication, will work without any issue.

Later on, I faced an issue. Suppose application A is running and if we will lock the device or if we will open another application, then application A will change its state.

Above given scenario will work for Windows, Android but not for iOS.

In this document, we will find the reason and solution to make it work for iOS using Qt.

App State Transition in iOS

In iOS, every App is the UIApplication Object, whose responsibility is to facilitate the interaction between the system and the other objects.

iOS app uses the model-view-controller architecture; Application controller has the following components:

  • UIApplication
  • Event Loop
  • View Controller
  • AppDelegate

AppDelegate works with UIApplication to handle the app initialization, state transitions and many high-level app events.

Execution States for Apps

State

Description

Not running

The app has not been launched or was running but was terminated by the system.

Inactive

The app is running in the foreground but is currently not receiving events. (It may be executing other code though.) An app usually stays in this state only briefly as it transitions to a different state.

Active

The app is running in the foreground and is receiving events. This is the normal mode for foreground apps.

Background

The app is in the background and executing code. Most apps enter this state briefly on their way to being suspended. However, an app that requests extra execution time may remain in this state for a period of time. In addition, an app being launched directly into the background enters this state instead of the inactive state.

Suspended

The app is in the background but is not executing code. The system moves apps to this state automatically and does not notify them before doing so. While suspended, an app remains in memory but does not execute any code. When a low-memory condition occurs, the system may purge suspended apps without notice to make more space for the foreground app

Every iOS app is always in one of the five states. The following diagram shows the state changes in iOS app.

Operating system manages the app state and it limits what an app can do in the background. If an app needs to continue running in background, then it should be handled properly.

Problem

When application goes into the background state, (e.g. battery usage optimization application performance, etc.) OS limits the functions that app can do in background.

For example, if an app which is using socket communication enters in background, then socket will be closed by OS (only iOS does this, this does not happen in Windows and Android) and socket communication resumes when app enters in Foreground. But it’s not the reality.

Qt also provides the enum which tells about current state of app. Following are the states:

State

Description

Qt::ApplicationSuspended The application is about to suspend. When entering this state, the application should save its state, cease all activities, and be prepared for code execution to stop. While suspended, the application can be killed at any time without further warnings (e.g. when low memory forces the OS to purge suspended applications).
Qt::ApplicationHidden The application is hidden and runs in the background. This is the normal state for applications that need to do background processing, like playing music, while the user interacts with other applications. The application should free up all graphical resources when entering this state.
Qt::ApplicationInactive The application is visible, but not selected to be in front. On desktop platforms, this typically means that the user activated another application. On mobile platforms, it is more common to enter this state when the OS is interrupting the user with e.g. incoming calls or SMS-messages. While in this state, consider reducing CPU-intensive tasks.
Qt::ApplicationActive The application is visible and selected to be in front.

Handling these states should resolve the issue on iOS platform. Unfortunately, I tried to handle these states in a sample program but the program was not getting the OS generated signal, i.e., app is in foreground, background, etc.

Solution

To save the app state when it enters into background and to reload the state on entering into foreground, I had to implement iOS specific implementation in my Qt application. I achieved this by integrating the Objective-C with C++ code. The following section will give a brief idea on this; I have no information on iOS development but developed a sample POC to show the integration of C++ and Objective-C code.

Create a QtQuick based project, i.e., AppDelegate and follow the following steps:

  1. Add class IOSAppState to the project
  2. Add AppDelegate.h file to the project
  3. Add AppDelegate.mm to the project

IOSAppState

This class will be used as an interaction layer between the main program and the Objective-C specific code block.

IOSAppState.h
C++
#ifndef IOSAPPSTATE_H
#define IOSAPPSTATE_H
#include <QObject>
void InitializeDelegate();
class IOSAppState : QObject
{
    Q_OBJECT
    explicit IOSAppState(QObject* parent=0);
    ~IOSAppState();
public:
    static IOSAppState* getInstance();
    void applicationDidEnterBackGround();
    void applicationDidEnterForeGround();
    void applicationDidBecomeActive();
    static void destroyInstance();
private:
    static IOSAppState* m_delegate;
};
#endif // IOSAPPSTATE_H
IOSAppState.cpp
C++
#include "iosappstate.h"
#include <QDebug>
IOSAppState* IOSAppState::m_delegate = nullptr;
IOSAppState::IOSAppState(QObject* parent): QObject(parent)
{
}
IOSAppState::~IOSAppState()
{
}
IOSAppState* IOSAppState::getInstance()
{
    if(nullptr == m_delegate)
    {
        m_delegate = new IOSAppState();
    }
    return m_delegate;
}
void IOSAppState::destroyInstance()
{
    if(m_delegate)
   {
        delete m_delegate;
        m_delegate = nullptr;
    }
}
void IOSAppState::applicationDidBecomeActive()
{
    qDebug()<<Q_FUNC_INFO;
}
void IOSAppState::applicationDidEnterBackGround()
{
    qDebug()<<Q_FUNC_INFO;
}
void IOSAppState::applicationDidEnterForeGround()
{
    qDebug()<<Q_FUNC_INFO;
}

IOSAppState will be a singleton class and will be used to establish the communication between iOS specific events and Qt Application.

Using Singleton instance of the IOSAppState, applicationDidBecomeActive(), applicationDidEnterBackGround and applicationDidEnterForeGround() can be called. Signals can be emitted from these methods and same events can be handled in the responsible module. For example, if we have to handle the network specific scenario, then there can be a Network Module which will have slots to do the operations on these signals.

I have put the debug messages just to track the application flow.

AppDelegate

AppDelegate.h
C++
#ifndef APPDELEGATE
#define APPDELEGATE
#import <UIKit/UIKit.h>
#import <iosappstate.h>

@interface AppDelegate : UIResponder <UIApplicationDelegate>
+(AppDelegate *)sharedAppDelegate;
@end
#endif // APPDELEGATE

It has the AppDelegate interface and property of AppDelegate* type which will be used to instantiate.

AppDelegate.mm

This is an implementation file which will have objective-c specific implementation of AppDelegate interface.

C++
#import "appdelegate.h"
#import "iosappstate.h"
@implementation AppDelegate
static AppDelegate *appDelegate = nil;
+(AppDelegate *)sharedAppDelegate{
    static dispatch_once_t pred;
    static AppDelegate *shared = nil;
    dispatch_once(&pred, ^{
        shared = [[super alloc] init];
    });
    return shared;
}
void InitializeDelegate ()
{
    //get app delegate.
    AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
    [[UIApplication sharedApplication] setDelegate:[AppDelegate sharedAppDelegate]];
    NSLog(@"Created a new appdelegate");
}

sharedAppDelegate()‘s responsibility will be to check if the AppDelegate instance has been instantiated or not. It will return the instance of AppDelegate* type.

InitializeDelegate () method’s role is to trigger the instantiation/execution of the objective-c code path. This method will basically retrieve the singleton app instance and the set the delegate.

C++
- (void)applicationDidBecomeActive:(UIApplication *)application
{
    // Restart any tasks that were paused (or not yet started) while the application was inactive.
    // If the application was previously in the background, optionally refresh the user interface.
    NSLog(@"In the Become Active");
    IOSAppState::getInstance()->applicationDidBecomeActive();
}
- (void)applicationWillTerminate:(UIApplication *)application
{
    // Called when the application is about to terminate. 
    // Save data if appropriate. See also applicationDidEnterBackground:.
}
- (BOOL)application:(UIApplication *)application 
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
    // Override point for customization after application launch.
    NSLog(@"DId this launch option happen");
        return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
    // Sent when the application is about to move from active to inactive state. 
    // This can occur for certain types of temporary interruptions 
    // (such as an incoming phone call or SMS message) or when the user quits 
    // the application and it begins the transition to the background state.
    // Use this method to pause ongoing tasks, disable timers, and throttle down 
    // OpenGL ES frame rates. Games should use this method to pause the game.
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
    // Use this method to release shared resources, save user data, 
    // invalidate timers, and store enough application state information 
    // to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, 
    // this method is called instead of applicationWillTerminate: when the user quits.
    NSLog(@"In the background");
    IOSAppState::getInstance()->applicationDidEnterBackGround();
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
    // Called as part of the transition from the background to the inactive state; 
    // here you can undo many of the changes made on entering the background.
        NSLog(@"In the foreground");
        IOSAppState::getInstance()->applicationDidEnterForeGround();
}
@end

Now, whenever application will enter in background or it will change its state; OS will trigger an event, i.e., applicationDidEnterBackground, applicationWillEnterForeground, etc.

From these events, we can make a call of IOSAppState’s member method to communicate with cpp based module.

Above given file implementation describes the basic Objective-c based code and how to communicate the same to .cpp based module.

main.cpp
C++
#include <QApplication>
#include <QQmlApplicationEngine>
#include "iosappstate.h"

int main(int argc, char *argv[])
{
    QApplication app(argc, argv);
    InitializeDelegate();
    QQmlApplicationEngine engine;
    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
    return app.exec();
}

Highlighted method, i.e., InitializeDelegate() is declared in IOSAppState.h. On making a call to this function will result in initialization of AppDelegate instance.

This is a simple program just to show the integration of iOS specific implementation with C++.

Conclusion

Though Qt is known for the cross platform development, sometimes we may get stuck with the OS specifics. There was no straight solution for such scenarios; I have to find a way to integrate both in one project and enable execution path based on the OS. This article is the outcome of such a scenario.

References

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)