iOS implementation
Let's use XCode, to write iOS code. Open XCode, by running this command from the root directory of your app:
xed ios
When workspace is opened, locate Pods
project and expand it. Search for Development Pods
and find AppInfoPackage
inside. When it's expanded, it will show all files that we created under app-info-package/ios
directory.
- ObjC++ & Swift
- ObjC++ only
AppInfoModuleImpl.swift
Let's start by creating a small pure Swift class that will directly retrieve all application informations:
import Foundation
@objc(AppInfoModuleImpl)
public class AppInfoModuleImpl : NSObject {
@objc public func getAppBuildNumber() -> String {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleVersion") as! String
}
@objc public func getAppBundleId() -> String {
return Bundle.main.bundleIdentifier!
}
@objc public func getAppVersion() -> String {
return Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") as! String
}
}
Here, we declare AppInfoModuleImpl
class, which has 3 synchronous methods.
To make Swift elements accessible to Objective-C world, we have to do 4 things:
- make class extending
NSObject
- mark class and its methods (at least those methods that are meant to be exposed) as public
- mark class with
@objc(exported-objc-name)
decorator - mark exposed methods with
@objc
decorator
Methods in the class are using Bundle
class, for the reference, check out Apple's dedicated docs section.
We will use main Bundle
instance to retrieve some information about the app.
In case of getAppBundleId
method, we will use bundleIdentifier
property, for other methods we use generic object(forInfoDictionaryKey:)
method, where the argument is a key from Info.plist
.
For possible Info.plist
keys, visit "About Info.plist keys and values".
AppInfoModule.h
Now, let's move to the module that will manage function calls from the JS world:
#import <React/RCTBridgeModule.h>
/**
* When using Swift classes in ObjC header, the class must have its
* "forward declaration"
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Include-Swift-Classes-in-Objective-C-Headers-Using-Forward-Declarations
*/
@class AppInfoModuleImpl;
/**
* Declare the ObjC interface for that native module class.
*
* It must extend NSObject (like every class in ObjC) and
* implement RCTBridgeModule (like each RN native module).
*/
@interface AppInfoModule : NSObject<RCTBridgeModule>
@end
AppInfoModule
class extends base NSObject
class (each class in Objective-C needs to extend NSObject
) and implements RCTBridgeModule
protocol (each RN iOS module needs to implement RCTBridgeModule
protocol).
Additionally, to use Swift AppInfoModuleImpl
class, we must do "forward-declaration" (check out Apple's Swift-ObjC interop dedicated docs section).
AppInfoModule.mm
#import "AppInfoModule.h"
/**
* When using Swift classes in ObjC implementation, the classes must be imported
* from generated Objective-C Interface Header
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Import-Code-Within-an-App-Target
*/
#import "AppInfoPackage-Swift.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "AppInfoPackage.h"
/**
* Each turbo module implements codegenerated spec protocol
*/
@interface AppInfoModule () <NativeAppInfoModuleSpec>
@end
#endif
/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule
/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeAppInfoModuleSpecJSI>(params);
}
#endif
@end
This is the implementation file for AppInfoModule
class.
Before declaring the implementation block, we have to import Swift-ObjC generated interface header (check out dedicated docs section),
and we have to implement code-generated spec protocol for new architecture (code between #if RCT_NEW_ARCH_ENABLED
& #endif
directives).
After that, inside implementation block we have RCT_EXPORT_MODULE
macro invoked with AppInfoModule
argument. This basically registers the module with provided name on iOS side, so that it's accessible in the JS world.
Also, for new architecture, it implements - getTurboModule:
method (from RCTTurboModule
protocol).
If you won't implement that method, XCode will complain that the code-generated spec protocol methods are not implemented.
Let's use AppInfoModuleImpl
class
// ...
/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [AppInfoModuleImpl new];
}
return self;
}
/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)
/**
* Declare if module should be initialized on the main queue
*/
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// ...
@end
First, we declare private variable on AppInfoModule
which will be initialized in the - init
method. We can use AppInfoModuleImpl
Swift class, because it's imported from AppInfoPackage-Swift.h
.
Next, we override static method + requiresMainQueueSetup
, which must be done whenever - init
method on the module class is overriden.
Finally, we can register exported methods with RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD
macro and return value from AppInfoModuleImpl
class:
// ...
/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}
// Exported methods are overriden - based on the spec class
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBuildNumber)
{
return [moduleImpl getAppBuildNumber];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBundleId)
{
return [moduleImpl getAppBundleId];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppVersion)
{
return [moduleImpl getAppVersion];
}
// ...
@end
Complete AppInfoModule.mm
file
#import "AppInfoModule.h"
/**
* When using Swift classes in ObjC implementation, the classes must be imported
* from generated Objective-C Interface Header
*
* @see https://developer.apple.com/documentation/swift/importing-swift-into-objective-c#Import-Code-Within-an-App-Target
*/
#import "AppInfoPackage-Swift.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "AppInfoPackage.h"
/**
* Each turbo module implements codegenerated spec protocol
*/
@interface AppInfoModule () <NativeAppInfoModuleSpec>
@end
#endif
/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [AppInfoModuleImpl new];
}
return self;
}
/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)
/**
* Declare if module should be initialized on the main queue
*/
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// Exported methods are overriden - based on the spec class
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBuildNumber)
{
return [moduleImpl getAppBuildNumber];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBundleId)
{
return [moduleImpl getAppBundleId];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppVersion)
{
return [moduleImpl getAppVersion];
}
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeAppInfoModuleSpecJSI>(params);
}
#endif
@end
AppInfoModuleImpl.h
Let's start by creating a small pure Objective-C class that will directly retrieve all application informations:
#import <Foundation/Foundation.h>
@interface AppInfoModuleImpl : NSObject
- (NSString *)getAppBuildNumber;
- (NSString *)getAppBundleId;
- (NSString *)getAppVersion;
@end
AppInfoModuleImpl.mm
#import "AppInfoModuleImpl.h"
/**
* Native module's shared implementation
*/
@implementation AppInfoModuleImpl
- (NSString *)getAppBuildNumber
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
}
- (NSString *)getAppBundleId
{
return [[NSBundle mainBundle] bundleIdentifier];
}
- (NSString *)getAppVersion
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}
@end
Here, we declare AppInfoModuleImpl
class, which has 3 synchronous methods.
Methods in the class are using NSBundle
class, for the reference, check out Apple's dedicated docs section.
We will use main NSBundle
instance to retrieve some information about the app.
In case of - getAppBundleId
method, we will use bundleIdentifier
property, for other methods we use generic - objectForInfoDictionaryKey:
method, where the argument is a key from Info.plist
.
For possible Info.plist
keys, visit "About Info.plist keys and values".
AppInfoModule.h
Now, let's move to the module that will manage function calls from the JS world:
#import <React/RCTBridgeModule.h>
/**
* Declare the ObjC interface for that native module class.
*
* It must extend NSObject (like every class in ObjC) and
* implement RCTBridgeModule (like each RN native module).
*/
@interface AppInfoModule : NSObject<RCTBridgeModule>
@end
AppInfoModule
class extends base NSObject
class (each class in Objective-C needs to extend NSObject
) and implements RCTBridgeModule
protocol (each RN iOS module needs to implement RCTBridgeModule
protocol).
AppInfoModule.mm
#import "AppInfoModule.h"
#import "AppInfoModuleImpl.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "AppInfoPackage.h"
/**
* Each turbo module implements codegenerated spec protocol
*/
@interface AppInfoModule () <NativeAppInfoModuleSpec>
@end
#endif
/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule
/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params {
return std::make_shared<facebook::react::NativeAppInfoModuleSpecJSI>(params);
}
#endif
@end
This is the implementation file for AppInfoModule
class.
Before declaring the implementation block, we have to import header file for AppInfoModuleImpl
class,
and we have to implement code-generated spec protocol for new architecture (code between #if RCT_NEW_ARCH_ENABLED
& #endif
directives).
After that, inside implementation block we have RCT_EXPORT_MODULE
macro invoked with AppInfoModule
argument. This basically registers the module with provided name on iOS side, so that it's accessible in the JS world.
Also, for new architecture, it implements - getTurboModule:
method (from RCTTurboModule
protocol).
If you won't implement that method, XCode will complain that the code-generated spec protocol methods are not implemented.
Let's use AppInfoModuleImpl
class
// ...
/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [AppInfoModuleImpl new];
}
return self;
}
/**
* Return the name of the module - it should match the name provided in JS specification
*/
RCT_EXPORT_MODULE(AppInfoModule)
/**
* Declare if module should be initialized on the main queue
*/
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// ...
@end
First, we declare private variable on AppInfoModule
which will be initialized in the - init
method. We can use AppInfoModuleImpl
Objective-C class, because it's imported from AppInfoModuleImpl.h
.
Next, we override static method + requiresMainQueueSetup
, which must be done whenever - init
method on the module class is overriden.
Finally, we can register exported methods with RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD
macro and return value from AppInfoModuleImpl
class:
// ...
/**
* Declare the ObjC implementation for that native module class
*/
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}
// Exported methods are overriden - based on the spec class
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBuildNumber)
{
return [moduleImpl getAppBuildNumber];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBundleId)
{
return [moduleImpl getAppBundleId];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppVersion)
{
return [moduleImpl getAppVersion];
}
// ...
@end
Complete AppInfoModule.mm
file
#import "AppInfoModule.h"
#import "AppInfoModuleImpl.h"
#if RCT_NEW_ARCH_ENABLED
/**
* Import header file with codegenerated protocols based on the JS specification
*
* The name of the header matches the name provided in codegenConfig's `name` field in package.json
*/
#import "AppInfoPackage.h"
// Each turbo module implements codegenerated spec protocol
@interface AppInfoModule () <NativeAppInfoModuleSpec>
@end
#endif
// Declare the ObjC implementation for that native module class
@implementation AppInfoModule {
AppInfoModuleImpl *moduleImpl;
}
// Return the name of the module - it should match the name provided in JS specification
RCT_EXPORT_MODULE(AppInfoModule)
- (instancetype)init {
self = [super init];
if (self) {
moduleImpl = [AppInfoModuleImpl new];
}
return self;
}
// Declare if module should be initialized on the main queue
+ (BOOL)requiresMainQueueSetup
{
return NO;
}
// Exported methods are overriden - based on the spec class
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBuildNumber)
{
return [moduleImpl getAppBuildNumber];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppBundleId)
{
return [moduleImpl getAppBundleId];
}
RCT_EXPORT_SYNCHRONOUS_TYPED_METHOD(NSString *, getAppVersion)
{
return [moduleImpl getAppVersion];
}
#if RCT_NEW_ARCH_ENABLED
// Implement RCTTurboModule protocol
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:
(const facebook::react::ObjCTurboModule::InitParams &)params
{
return std::make_shared<facebook::react::NativeAppInfoModuleSpecJSI>(params);
}
#endif
@end
You can check training repo for Objective-C & Swift implementation here and Objective-C-only implementation here.
That's iOS part, now let's go to Android!