Skip to main content

In-app Updates

In-app updates enable applications to deliver and install updates directly within the app, enhancing user experience by minimizing disruption.

What are In-app Updates for Streamlined Update Experience

In-app updates offer a seamless method for delivering and installing new versions of an application directly within the app. This eliminates the need for users to manually check for updates, ensuring they receive the latest features and fixes efficiently. This streamlined approach enhances the overall user experience and keeps the app up-to-date.

Benefits and Examples of In-app Updates

In-app updates offer several benefits, including a smoother user experience by enabling seamless updates without requiring users to manually download or install new versions. For example, critical bug fixes and feature enhancements can be automatically applied while the app is running, ensuring users always have access to the latest improvements and functionalities.

Implementing In-App Updates

Authentication Methods for Obtaining Appcircle Personal API Token

There are two primary methods to implement authentication and retrieve the Appcircle Personal API token for in-app updates:

  1. Using a Custom Backend Endpoint
  2. Using Appcircle Services

1. Using a Custom Backend Endpoint

This method involves creating a secure backend service that handles the authentication process and retrieves the Appcircle Personal API token on behalf of your app. Here's how it works:

  1. Your app sends a request to your custom backend endpoint Enterprise App Store profile id with authentication credentials such as email and password.
  2. The backend authenticates with Appcircle using profile-specific app secret and obtains the Personal API token.
  3. The backend returns the token to your app.

Benefits of this approach:

  • Enhanced security as sensitive credentials are not stored in the app
  • Centralized management of authentication
  • Ability to implement additional security measures on the backend

Sample Backend Project:

Preview of GitHub - appcircleio/in-app-update-backend-sample: Seamless in-app update experience using Appcircle Enterprise App Store

2. Using Appcircle Services

This method involves directly using Appcircle's authentication services from within your app. Here's how it works:

  1. Your app securely stores the profile-specific secret and profile id.
  2. The app sends the secret along with the profile ID to Appcircle authentication services.
  3. Appcircle validates the credentials and returns the necessary authentication token.
  4. Upon successful authentication, the app receives the Personal API token.

Benefits of this approach:

  • Simpler implementation with fewer components
  • Reduced backend maintenance
  • Direct integration with Appcircle services

Both methods have their merits, and the choice depends on your specific security requirements, infrastructure, and development preferences. The custom backend approach offers more control and security, while the direct Appcircle services method provides a more straightforward implementation.

Prerequisites for Integration

Authentication Requirements

To integrate an in-app update experience, you will need the profile secret, the enterprise store prefix, the enterprise store url, and the enterprise store profile id.

How to Obtain Integrations Parameters

In-app Update Secret

Steps to Generate a Profile-Specific Secret:

1- Navigate to your enterprise app store profile.

2- In the top-right corner, click on the Settings icon.

3- Select Generate Secret to create a profile-specific secret.

Enterprise Store Prefix

Navigate to the Enterprise Store module and settings page to find the STORE PREFIX information. You can also modify it if needed.

Enterprise Store URL

Navigate to the Enterprise Store module and settings page to find the STORE URL information.

Enterprise Store Profile Id

You can obtain your Enterprise Store Profile ID from the URL or by using the @appcircle/cli.

How to Extract Your Enterprise Store Profile ID from the URL
  1. Navigate to your Enterprise Store Profile.
  2. Check the URL, which should be in this format: /enterprise-store/profiles/PROFILE_ID. The PROFILE_ID refers to your specific profile ID.
Retrieving Profile ID Using @appcircle/cli

The upcoming command retrieves the complete list of Enterprise Store Profiles.

appcircle enterprise-app-store profile list

Authentication for Updates

Retrieving Access Token Using Personal API Token

To fetch app versions and download the binary, you first need to obtain an access token using a Personal API Token (PAT).

extension API {
func getAccessToken(secret: String, profileId: String) async throws -> AuthModel {
var components = URLComponents()
components.scheme = apiConfig.scheme
components.host = apiConfig.host
components.path = "/api/auth/token"

guard let url = components.url else {
throw HTTPError.invalidUrl
}

var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.POST.rawValue
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")

let parameters: [String: Any] = [
"ProfileId": profileId,
"Secret": secret
]

request.httpBody = try? JSONSerialization.data(withJSONObject: parameters)

return try await apiFetcher.request(request: request)
}
}

Initiating Updates

Retrieving Available App Versions from Your Enterprise Store

Fetch all available versions and compare them with the current version to determine if an update is required.

extension API {
func getAppVersions(accessToken: String) async throws -> [AppVersion] {
var components = URLComponents()
components.scheme = apiConfig.scheme
components.host = apiConfig.host
components.path = "/api/app-versions"
guard let url = components.url else {
throw HTTPError.invalidUrl
}
var request = URLRequest(url: url)
request.httpMethod = HTTPMethod.GET.rawValue
request.setValue("*/*", forHTTPHeaderField: "Accept")
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

return try await apiFetcher.request(request: request)
}
}

Compare Current Version with Fetched App Versions to Identify Updates

Compare the current version with the fetched versions to identify the latest release. Configuration options can be adjusted to determine which version is considered the latest.

/*
You can implement your custom update check mechanism within this function.
Currently, we convert the version to an integer and compare it with the 'CFBundleShortVersionString'.
You may want to check other datas about the app version to write the update control mechanism please check
/v2/profiles/{profileId}/app-versions at https://api.appcircle.io/openapi/index.html?urls.primaryName=store
*/
private func getLatestVersion(currentVersion: String, appVersions: [AppVersion]) -> AppVersion? {
var latestAppVersion: AppVersion?
let currentComponents = versionComponents(from: currentVersion)

// Helper function to convert version string into an array of integers
func versionComponents(from version: String) -> [Int] {
return version.split(separator: ".").compactMap { Int($0) }
}


appVersions.forEach { app in
// Convert versions to arrays of integers
let latestComponents = versionComponents(from: app.version)

// Compare versions component by component
for (current, latest) in zip(currentComponents, latestComponents) {
// You can control to update None, Beta or Live publish types you have selected on Appcircle Enterprise Store
if (latest > current && app.publishType != 0) {
latestAppVersion = app
}

}
}

return latestAppVersion
}

Updating the App

If a newer version is available, generate the platform-specific download URL and return it for background opening later.

func checkForUpdate(secret: String, profileId: String, storeURL: String, userEmail: String) async throws -> URL? {
do {
let authResponse = try await self.authApi.getAccessToken(secret: secret, profileId: profileId)
let appVersions = try await self.api.getAppVersions(accessToken: authResponse.accessToken)
let bundle = Bundle.main
let currentVersion = bundle.infoDictionary?["CFBundleShortVersionString"] as? String
guard let currentVersion = currentVersion else {
print("'CFBundleShortVersionString' Version Could Not found")
return nil
}

guard let availableVersion = getLatestVersion(currentVersion: currentVersion, appVersions: appVersions) else {
print("App is up to date!")
return nil
}

guard let downloadURL = URL(string: "itms-services://?action=download-manifest&url=https://\(storeURL)/api/app-versions/\(availableVersion.id)/download-version/\(authResponse.accessToken)/user/\(userEmail)") else {
print("Latest Version URL could not created")
return nil
}

return downloadURL
} catch {
print(error)
return nil
}
}

How to Prompt an Alert and Install the Latest Release

After obtaining the download URL for a newer version, display an alert with options to update or cancel. Customize the alert based on your requirements, such as omitting the cancel button for mandatory updates.

import SwiftUI

@main
struct AppcircleApp: App {
@State private var updateURL: URL?
@State private var showAlert: Bool = false


var body: some Scene {
WindowGroup {
ContentView()
.onAppear {
let updateChecker = UpdateChecker()
Task {
if let updateURL = try await updateChecker.checkForUpdate(secret: Environments.secret, profileId: Environments.profileId, storeURL: Environments.storeURL, userEmail: "USER_EMAIL") {
self.updateURL = updateURL
self.showAlert.toggle()
}
}
}
.alert(isPresented: $showAlert) {
Alert(
title: Text("Update Available"),
message: Text("A new version is available Would you like to update?"),
primaryButton: .default(Text("Update"), action: {
UIApplication.shared.open(self.updateURL!) { isOpened in
print("Application Opened")
}
}),
secondaryButton: .cancel(Text("Cancel"), action: {
// Handle the cancel action
print("User canceled the update")
})
)
}
}
}
}
caution

With API Level 29 and above, the in-app update experience must be managed by allowing users to download and manually install the update due to increased security restrictions.