Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. Mobile and Embedded
  4. How to automatically launch iOS app with parameters

How to automatically launch iOS app with parameters

Scheduled Pinned Locked Moved Solved Mobile and Embedded
8 Posts 4 Posters 1.6k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M Offline
    M Offline
    Martin Burchell
    wrote on last edited by
    #1

    Qt 5.12

    I'd like to launch our iOS app with some optional parameters unique to the user (an operating mode, a server URL and access key). If the app is launched without these, the user is prompted to enter these on startup. On the desktop, these parameters can be specified as arguments on the command line.

    It looks like the way to do this on iOS is through a URL with a custom scheme. I've added CFBundleURLTypes to the Info.plist and I can now launch my app from a browser with a custom URL like:

    camcops://default_single_user_mode//default_server_location/https%3A%2F%2Fserver.example.com%2Fapi/default_access_key/fomom-nobij-hirug-hukor-rudal-nukup-kilum-fanif-b

    So far so good.

    The bit I'm struggling with is how to read the URL parameters and pass them into the application. I can see there is the setUrlHandler() method on the QDesktopServices class documented at https://doc.qt.io/qt-5/qdesktopservices.html and referenced at https://stackoverflow.com/questions/28822086/best-way-to-get-source-url-of-custom-ios-scheme-in-qml but if I set the URL handler once the app is launched it will be too late won't it?

    Elsewhere I see people extending QIOSApplicationDelegate but I don't understand how this would glue together with my application.

    Has anyone done this before? Am I going about it the right way?

    1 Reply Last reply
    0
    • M Offline
      M Offline
      Martin Burchell
      wrote on last edited by
      #2

      I got it working for a URL like camcops://camcops.org/register/?default_single_user_mode=true&default_server_location=https%3A%2F%2Fserver.example.com%2Fapi&default_access_key=abcde-fghij-klmno-pqrst-uvwxy-zabcd-efghi-jklmn-o and the same on Android but with http scheme. That seems to be the preferred approach on each platform. Unfortunately some email clients such as GMail do not display URLs with unknown schemes as hyperlinks, even if they are within <a> elements in HTML.

      Full code is at https://github.com/RudolfCardinal/camcops/pull/185 under GPL V3

      Here are some highlights:

      Info.plist:

      <key>CFBundleURLTypes</key>
        <array>
          <dict>
            <key>CFBundleTypeRole</key>
            <string>Viewer</string>
            <key>CFBundleURLSchemes</key>
            <array>
              <string>camcops</string>
            </array>
          </dict>
        </array>
      

      In the main application:

         auto url_handler = UrlHandler::getInstance();
         connect(url_handler, &UrlHandler::defaultSingleUserModeSet,
                 this, &CamcopsApp::setDefaultSingleUserMode);
         connect(url_handler, &UrlHandler::defaultServerLocationSet,
                 this, &CamcopsApp::setDefaultServerLocation);
         connect(url_handler, &UrlHandler::defaultAccessKeySet,
                 this, &CamcopsApp::setDefaultAccessKey);
      

      urlhandler.cpp:

      UrlHandler* UrlHandler::m_instance = NULL;
      
      UrlHandler::UrlHandler()
      {
          m_instance = this;
          QDesktopServices::setUrlHandler("camcops", this, "handleUrl");
      }
      
      void UrlHandler::handleUrl(const QUrl url)
      {
          auto query = QUrlQuery(url);
          auto default_single_user_mode = query.queryItemValue("default_single_user_mode");
          if (!default_single_user_mode.isEmpty()) {
              emit defaultSingleUserModeSet(default_single_user_mode);
          }
      
          auto default_server_location = query.queryItemValue("default_server_location",
                                                              QUrl::FullyDecoded);
          if (!default_server_location.isEmpty()) {
              emit defaultServerLocationSet(default_server_location);
          }
      
          auto default_access_key = query.queryItemValue("default_access_key");
          if (!default_access_key.isEmpty()) {
              emit defaultAccessKeySet(default_access_key);
          }
      }
      
      
      UrlHandler* UrlHandler::getInstance()
      {
          if (!m_instance)
              m_instance = new UrlHandler;
          return m_instance;
      }
      

      Android doesn't support the same approach so we have to write some Java:

      public class CamcopsActivity extends QtActivity
      {
          // Defined in urlhandler.cpp
          public static native void handleAndroidUrl(String url);
      
          @Override
          public void onCreate(Bundle savedInstanceState) {
              // Called when no instance of the app is running. Pass URL parameters
              // as arguments to the app's main()
              Intent intent = getIntent();
      
              if (intent != null && intent.getAction() == Intent.ACTION_VIEW) {
                  Uri uri = intent.getData();
                  if (uri != null) {
                      Log.i(TAG, intent.getDataString());
      
                      Map<String, String> parameters = getQueryParameters(uri);
               
                      StringBuilder sb = new StringBuilder();
      
                      String separator = "";
                      for (Map.Entry<String, String> entry : parameters.entrySet()) {
                          String name = entry.getKey();
                          String value = entry.getValue();
                          if (value != null) {
                              sb.append(separator)
                                  .append("--").append(name)
                                  .append("=").append(value);
      
                              separator = "\t";
                          }
                      }
      
                      APPLICATION_PARAMETERS = sb.toString();
                  }
              }
      
              super.onCreate(savedInstanceState);
          }
      
          @Override
          public void onNewIntent(Intent intent) {
              /* Called when the app is already running. Send the URL parameters
               * as signals to the app.
               */
              super.onNewIntent(intent);
      
              sendUrlToApp(intent);
          }
      
          private void sendUrlToApp(Intent intent) {
              String url = intent.getDataString();
      
              if (url != null) {
                  handleAndroidUrl(url);
              }
          }
      
          private Map<String, String> getQueryParameters(Uri uri) {
              List<String> names = Arrays.asList("default_single_user_mode",
                                                 "default_server_location",
                                                 "default_access_key");
      
              Map<String, String> parameters = new HashMap<String, String>();
      
              for (String name : names) {
                  String value = uri.getQueryParameter(name);
                  if (value != null) {
                      parameters.put(name, value);
                  }
              }
      
              return parameters;
          }
      

      urlhandler.cpp:

      #ifdef Q_OS_ANDROID
      // Called from android/src/org/camcops/camcops/CamcopsActivity.java
      #ifdef __cplusplus
      extern "C" {
      #endif
      
      JNIEXPORT void JNICALL
        Java_org_camcops_camcops_CamcopsActivity_handleAndroidUrl(
            JNIEnv *env,
            jobject obj,
            jstring url)
      {
          Q_UNUSED(obj)
      
          const char *url_str = env->GetStringUTFChars(url, NULL);
      
          UrlHandler::getInstance()->handleUrl(QUrl(url_str));
      
          env->ReleaseStringUTFChars(url, url_str);
      }
      
      #ifdef __cplusplus
      }
      #endif
      
      #endif
      

      and then in AndroidManifest.xml:

      <activity ... android:name="org.camcops.camcops.CamcopsActivity" ... launchMode="singleTask">
      ...
         <intent-filter>
             <action android:name="android.intent.action.VIEW"/>
             <category android:name="android.intent.category.DEFAULT"/>
             <category android:name="android.intent.category.BROWSABLE"/>
             <data android:scheme="http" android:host="camcops.org" android:path="/register/"/>
         </intent-filter>
      </activity>
      
      P 1 Reply Last reply
      2
      • SGaistS Offline
        SGaistS Offline
        SGaist
        Lifetime Qt Champion
        wrote on last edited by
        #3

        Hi,

        Wouldn't it be simpler to store these in settings and load them from there ?

        Interested in AI ? www.idiap.ch
        Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

        1 Reply Last reply
        0
        • M Offline
          M Offline
          Martin Burchell
          wrote on last edited by
          #4

          @SGaist Possibly. I wasn't aware of QSettings. Now I am. Thank you.

          This defaults are only needed the first time the app is used for registration. Any other user settings we store encrypted in a SQLite database.

          1 Reply Last reply
          0
          • SGaistS Offline
            SGaistS Offline
            SGaist
            Lifetime Qt Champion
            wrote on last edited by
            #5

            I may have misunderstood something. You users will receive these information in an email to get started ?

            Interested in AI ? www.idiap.ch
            Please read the Qt Code of Conduct - https://forum.qt.io/topic/113070/qt-code-of-conduct

            M 1 Reply Last reply
            0
            • SGaistS SGaist

              I may have misunderstood something. You users will receive these information in an email to get started ?

              M Offline
              M Offline
              Martin Burchell
              wrote on last edited by
              #6

              @SGaist Yes exactly. It's an app for completing medical tasks such as questionnaires. The patients are set up on a server and emailed a URL with a unique access key and a server address. The first time the app is used, it registers with the server and downloads the questionnaires. After that the URL is no longer needed.

              1 Reply Last reply
              0
              • M Martin Burchell

                I got it working for a URL like camcops://camcops.org/register/?default_single_user_mode=true&default_server_location=https%3A%2F%2Fserver.example.com%2Fapi&default_access_key=abcde-fghij-klmno-pqrst-uvwxy-zabcd-efghi-jklmn-o and the same on Android but with http scheme. That seems to be the preferred approach on each platform. Unfortunately some email clients such as GMail do not display URLs with unknown schemes as hyperlinks, even if they are within <a> elements in HTML.

                Full code is at https://github.com/RudolfCardinal/camcops/pull/185 under GPL V3

                Here are some highlights:

                Info.plist:

                <key>CFBundleURLTypes</key>
                  <array>
                    <dict>
                      <key>CFBundleTypeRole</key>
                      <string>Viewer</string>
                      <key>CFBundleURLSchemes</key>
                      <array>
                        <string>camcops</string>
                      </array>
                    </dict>
                  </array>
                

                In the main application:

                   auto url_handler = UrlHandler::getInstance();
                   connect(url_handler, &UrlHandler::defaultSingleUserModeSet,
                           this, &CamcopsApp::setDefaultSingleUserMode);
                   connect(url_handler, &UrlHandler::defaultServerLocationSet,
                           this, &CamcopsApp::setDefaultServerLocation);
                   connect(url_handler, &UrlHandler::defaultAccessKeySet,
                           this, &CamcopsApp::setDefaultAccessKey);
                

                urlhandler.cpp:

                UrlHandler* UrlHandler::m_instance = NULL;
                
                UrlHandler::UrlHandler()
                {
                    m_instance = this;
                    QDesktopServices::setUrlHandler("camcops", this, "handleUrl");
                }
                
                void UrlHandler::handleUrl(const QUrl url)
                {
                    auto query = QUrlQuery(url);
                    auto default_single_user_mode = query.queryItemValue("default_single_user_mode");
                    if (!default_single_user_mode.isEmpty()) {
                        emit defaultSingleUserModeSet(default_single_user_mode);
                    }
                
                    auto default_server_location = query.queryItemValue("default_server_location",
                                                                        QUrl::FullyDecoded);
                    if (!default_server_location.isEmpty()) {
                        emit defaultServerLocationSet(default_server_location);
                    }
                
                    auto default_access_key = query.queryItemValue("default_access_key");
                    if (!default_access_key.isEmpty()) {
                        emit defaultAccessKeySet(default_access_key);
                    }
                }
                
                
                UrlHandler* UrlHandler::getInstance()
                {
                    if (!m_instance)
                        m_instance = new UrlHandler;
                    return m_instance;
                }
                

                Android doesn't support the same approach so we have to write some Java:

                public class CamcopsActivity extends QtActivity
                {
                    // Defined in urlhandler.cpp
                    public static native void handleAndroidUrl(String url);
                
                    @Override
                    public void onCreate(Bundle savedInstanceState) {
                        // Called when no instance of the app is running. Pass URL parameters
                        // as arguments to the app's main()
                        Intent intent = getIntent();
                
                        if (intent != null && intent.getAction() == Intent.ACTION_VIEW) {
                            Uri uri = intent.getData();
                            if (uri != null) {
                                Log.i(TAG, intent.getDataString());
                
                                Map<String, String> parameters = getQueryParameters(uri);
                         
                                StringBuilder sb = new StringBuilder();
                
                                String separator = "";
                                for (Map.Entry<String, String> entry : parameters.entrySet()) {
                                    String name = entry.getKey();
                                    String value = entry.getValue();
                                    if (value != null) {
                                        sb.append(separator)
                                            .append("--").append(name)
                                            .append("=").append(value);
                
                                        separator = "\t";
                                    }
                                }
                
                                APPLICATION_PARAMETERS = sb.toString();
                            }
                        }
                
                        super.onCreate(savedInstanceState);
                    }
                
                    @Override
                    public void onNewIntent(Intent intent) {
                        /* Called when the app is already running. Send the URL parameters
                         * as signals to the app.
                         */
                        super.onNewIntent(intent);
                
                        sendUrlToApp(intent);
                    }
                
                    private void sendUrlToApp(Intent intent) {
                        String url = intent.getDataString();
                
                        if (url != null) {
                            handleAndroidUrl(url);
                        }
                    }
                
                    private Map<String, String> getQueryParameters(Uri uri) {
                        List<String> names = Arrays.asList("default_single_user_mode",
                                                           "default_server_location",
                                                           "default_access_key");
                
                        Map<String, String> parameters = new HashMap<String, String>();
                
                        for (String name : names) {
                            String value = uri.getQueryParameter(name);
                            if (value != null) {
                                parameters.put(name, value);
                            }
                        }
                
                        return parameters;
                    }
                

                urlhandler.cpp:

                #ifdef Q_OS_ANDROID
                // Called from android/src/org/camcops/camcops/CamcopsActivity.java
                #ifdef __cplusplus
                extern "C" {
                #endif
                
                JNIEXPORT void JNICALL
                  Java_org_camcops_camcops_CamcopsActivity_handleAndroidUrl(
                      JNIEnv *env,
                      jobject obj,
                      jstring url)
                {
                    Q_UNUSED(obj)
                
                    const char *url_str = env->GetStringUTFChars(url, NULL);
                
                    UrlHandler::getInstance()->handleUrl(QUrl(url_str));
                
                    env->ReleaseStringUTFChars(url, url_str);
                }
                
                #ifdef __cplusplus
                }
                #endif
                
                #endif
                

                and then in AndroidManifest.xml:

                <activity ... android:name="org.camcops.camcops.CamcopsActivity" ... launchMode="singleTask">
                ...
                   <intent-filter>
                       <action android:name="android.intent.action.VIEW"/>
                       <category android:name="android.intent.category.DEFAULT"/>
                       <category android:name="android.intent.category.BROWSABLE"/>
                       <data android:scheme="http" android:host="camcops.org" android:path="/register/"/>
                   </intent-filter>
                </activity>
                
                P Offline
                P Offline
                pedritorres21
                Banned
                wrote on last edited by pedritorres21
                #7
                This post is deleted!
                1 Reply Last reply
                0
                • D Offline
                  D Offline
                  danryu
                  wrote on last edited by danryu
                  #8
                  This post is deleted!
                  1 Reply Last reply
                  0

                  • Login

                  • Login or register to search.
                  • First post
                    Last post
                  0
                  • Categories
                  • Recent
                  • Tags
                  • Popular
                  • Users
                  • Groups
                  • Search
                  • Get Qt Extensions
                  • Unsolved