[Q] NullPointerException when trying to access MainActivity from different class - Android Q&A, Help & Troubleshooting

Hi!
I am trying to develop an android app with a google map v2, location service and some control buttons.
But I don't want to put all these things inside one MainActivity class. So I thought I could split all the code into some more classes. The MainActivity shall controll all the GUI things and react on map or location events...
Now I have the following problem. Inside my onCreate I instanziate the additional classes:
Code:
// Preferences as singleton
pref = Prefs.getInstance(this.getApplicationContext());
pref.loadSettings();
// Set up the location
loc = new Locations(pref);
loc.setCallback(this);
map = new MyMap(pref);
It seems to work fine. But inside the MyMap class every time I start the app a null pointer exception is thrown. When I am calling MyMap() the following code will be executed:
Code:
[...]
private Prefs pref;
private GoogleMap mMap;
[...]
public MyMap(Prefs prefs) {
pref = (Prefs) prefs;
if (mMap == null) {
FragmentManager fmanager = getSupportFragmentManager();
mMap = ((SupportMapFragment) fmanager.findFragmentById(R.id.map)).getMap();
[...]
}
The line with the findFragmentById is the one that causes the exception.
If I write
Code:
SupportMapFragment f = ((SupportMapFragment) fmanager.findFragmentById(R.id.map));
f is allways null. But how can I access the fragments and view elements defined within my MainActivity?
It works if I put the code inside my MainAcitivity.
Every class extends "android.support.v4.app.FragmentActivity"
I tried to save the application context within my Prefs() class, so that I can access it from everywhere.
But I don't know how to use it inside my additional classes.
How to share the "R" across all my classes?
Can someone help me please?
Thank you very much!!
Thorsten

Are you having trouble adding a Map to a Fragment? If so, then you may take a look at this tutorial. I haven't tried it myself since I couldn't install Google Play Services on my development device. If it helps, do write back, as I am definitely going to try it myself soon.

Related

Build can’t find dependency injection from Foursquare library in Graddle

Hello guys: I’m using the next scenario for Android native applications development:
Android Studio 3.6.2
Graddle plugin 3.6.2
Graddle version 5.6.4
Kotlin version 1.3.61
When I make this dependency injection in my build.graddle
Implementation 'com.foursquare:foursquare-android-oauth:1.1.0'
The next class: android.net.ConnectivityManager
can’t be found in the build process. Or at least it hasn't the expected properties.
I have implemented the next method to detect internet connection:
package com.example.ejoauth.networkconnection
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities
class NetworkConnectionStatus {
companion object {
// This code depends on the target SDK version
fun isInternetAvailable(context: Context): Boolean {
var result : Boolean = false
val connectivityManager: android.net.ConnectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as android.net.ConnectivityManager
val network : android.net.Network = connectivityManager.activeNetwork ?: return false // <---- Error
val nwCap : android.net.NetworkCapabilities = connectivityManager.getNetworkCapabilities(network) ?: return false // <---- Error
result = when {
nwCap.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) -> true
nwCap.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) -> true
nwCap.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) -> true
else -> false
}
return result
}
}
}
In build process, the next errors are thrown (only appears after "Foursquare" dependency injection)
D:\clcwork\kotlin\EjOauth\app\src\main\java\com\example\ejoauth\networkconnection\NetworkConnectionStatus.kt: (15, 69): Unresolved reference: activeNetwork D:\clcwork\kotlin\EjOauth\app\src\main\java\com\example\ejoauth\networkconnection\NetworkConnectionStatus.kt: (16, 79): Unresolved reference: getNetworkCapabilities
It seems like "Foursquare" libraries overwrite some clases or packages with another implementation in which attributes or methods are lost. The only solution I have found so far is not implementing this method “NetworkConnectionStatus.isInternetAvailable”. But this is not a solution. As you can see, I'm indicating packages explicitly in my code.
Can you help me, please?
Thank you in advance.

Getting Place Details with HMS Site Kit

If we briefly talk about what HMS Site Kit is, you can provide users to explore the world faster with Site Kit. You can search for locations by keywords, find places which are close to the specified coordinate point, get detailed information about a place and get suggestions for places by keyword.
We can get detailed information about a place with Place Detail Search, another feature of Site Kit. The only condition for this, we need to know Site model’s id value that belongs to the place we want to search.
Before I explain the use of Place Detail Search, I would like to share with you a function that we can use this feature.
Code:
fun placeDetail(siteId: String){
val searchService = SearchServiceFactory.create(context,
URLEncoder.encode(
"Your-API-KEY",
"utf-8"))
var request = DetailSearchRequest()
request.siteId = siteId
request.language = Locale.getDefault().language // Getting system language
searchService.detailSearch(request, object: SearchResultListener<DetailSearchResponse>{
override fun onSearchError(searchStatus: SearchStatus?) {
Log.e("SITE_KIT","${searchStatus?.errorCode} - ${searchStatus?.errorMessage}")
}
override fun onSearchResult(detailSearchResponse: DetailSearchResponse?) {
var site = detailSearchResponse?.site
site?.let {
Log.i("SITE_KIT", "Name => ${it.name}," +
"Format address => ${it.formatAddress}, " +
"Coordinate => ${it.location.lat} - ${it.location.lng}, " +
"Phone => ${it.poi.phone}, " +
"Photo URLS => ${it.poi.photoUrls}, " +
"Rating => ${it.poi.rating}, " +
"Address Detail => ${it.address.thoroughfare}, ${it.address.subLocality}, " +
"${it.address.locality}, ${it.address.adminArea}, ${it.address.country}")
} ?: kotlin.run {
Log.e("SITE_KIT","Site Place couldn't find with the given site ID")
}
}
})
}
First, we need to create a SearchService object from the SearchServiceFactory class. For this, we can use the create() method of the SearchServiceFactory class. We need to declare two parameters in create() method.
The first of these parameters is context value. It is recommended that Context value should be in Activity type. Otherwise, when HMS Core(APK) needs to be updated, we can not receive any notification about it.
The second parameter is API Key value that we can access via AppGallery Connect. This value is generated automatically by AppGallery Connect when a new app is created. We need to encode API parameter as encodeURI.
After creating our SearchService object as I described above, we can create a DetailSearchRequest object. We will specify the necessary parameters on this object related to the place which we want want to get information.
After creating our DetailSearchRequest object, we can determine parameters for a place that we want to get information. Two parameters are specified here:
SiteId: There is a unique id value for each Site in Site Kit. This parameter is used to specify the id value of the place whose information is to be obtained.
Language: It is used to specify the language that search results have to be returned. If this parameter is not specified, language of the query field we have specified in the query field is accepted by default. In example code snippet in above, language of device has been added automatically in order to get a healthy result.
After entering the id value and language parameter of the place that we want to learn in detail, we can start learning the details. For this, we will use detailSearch() method of the SearchService object. This method takes two parameters.
For the first parameter, we must specify DetailSearchRequest object we have defined above.
For the second parameter, we have to implement SearchResultListener interface. Since this interface has a generic structure, we need to specify class belonging to the values to be returned. We can get the incoming values by specifying DetailSearchResponse object. Two methods should be override with this interface. onSearchError() method is executed if operation fails, and onSearchResult() method is executed if operations is successful. There is one value in DetailSearchResponse. This value is Site object that belongs to the id value. With the Site variable of DetailSearchResponse object, we can access information belong to place we have searched.
sujith.e said:
Hi,Why API key is required?
Click to expand...
Click to collapse
API key is a simple credential for accessing Huawei services. Your API key is creating automatically on the AppGallery Connect when you create an application, and then your app can use the key to call public APIs provided by Huawei.
When an app calls a public API provided by Huawei, we should give this information to API to help Huawei to identify our application.

React Native Startup Speed Optimization - Native Chapter (Including Source Code Analysis)

0. React Native Startup Process
React Native is a web front-end friendly hybrid development framework that can be divided into two parts at startup:
· Running of Native Containers
· Running of JavaScript code
The Native container is started in the existing architecture (the version number is less than 1.0.0). The native container can be divided into three parts:
· Native container initialization
· Full binding of native modules
· Initialization of JSEngine
After the container is initialized, the stage is handed over to JavaScript, and the process can be divided into two parts:
· Loading, parsing, and execution of JavaScript code
· Construction of JS components
Finally, the JS Thread sends the calculated layout information to the Native end, calculates the Shadow Tree, and then the UI Thread performs layout and rendering.
I have drawn a diagram of the preceding steps. The following table describes the optimization direction of each step from left to right:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Note: During React Native initialization, multiple tasks may be executed concurrently. Therefore, the preceding figure only shows the initialization process of React Native and does not correspond to the execution sequence of the actual code.
1. Upgrade React Native
The best way to improve the performance of React Native applications is to upgrade a major version of the RN. After the app is upgraded from 0.59 to 0.62, no performance optimization is performed on the app, and the startup time is shortened by 1/2. When React Native's new architecture is released, both startup speed and rendering speed will be greatly improved.
2. Native container initialization
Container initialization must start from the app entry file. I will select some key code to sort out the initialization process.
iOS source code analysis
1.AppDelegate.m
AppDelegate.m is the entry file of the iOS. The code is simple. The main content is as follows:
// AppDelegate.m
- (BOOL)applicationUIApplication *)application didFinishLaunchingWithOptionsNSDictionary *)launchOptions {
// 1. Initialize a method for loading jsbundle by RCTBridge.
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
// 2. Use RCTBridge to initialize an RCTRootView.
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName"RN64"
initialProperties:nil];
// 3. Initializing the UIViewController
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
// 4. Assigns the value of RCTRootView to the view of UIViewController.
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
In general, looking at the entry document, it does three things:
Ø Initialize an RCTBridge implementation method for loading jsbundle.
Ø Use RCTBridge to initialize an RCTRootView.
Ø Assign the value of RCTRootView to the view of UIViewController to mount the UI.
From the entry source code, we can see that all the initialization work points to RCTRootView, so let's see what RCTRootView does.
2.RCTRootView
Let's take a look at the header file of RCTRootView first. Let's just look at some of the methods we focus on:
/ RCTRootView.h
@interface RCTRootView : UIView
// Initialization methods used in AppDelegate.m
- (instancetype)initWithBridgeRCTBridge *)bridge
moduleNameNSString *)moduleName
initialPropertiesnullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
From the header file:
Ø RCTRootView inherits from UIView, so it is essentially a UI component;
Ø When the RCTRootView invokes initWithBridge for initialization, an initialized RCTBridge must be transferred.
In the RCTRootView.m file, initWithBridge listens to a series of JS loading listening functions during initialization. After listening to the completion of JS Bundle file loading, it invokes AppRegistry.runApplication() in JS to start the RN application.
We find that RCTRootView.m only monitors various events of RCTBridge, but is not the core of initialization. Therefore, we need to go to the RCTBridge file.
3.RCTBridge.m
In RCTBridge.m, the initialization invoking path is long, and the full pasting source code is long. In short, the last call is (void)setUp. The core code is as follows:
- (Class)bridgeClass
{
return [RCTCxxBridge class];
}
- (void)setUp {
// Obtains the bridgeClass. The default value is RCTCxxBridge.
Class bridgeClass = self.bridgeClass;
// Initializing the RTCxxBridge
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
// Starting RTCxxBridge
[self.batchedBridge start];
}
We can see that the initialization of the RCTBridge points to the RTCxxBridge.
4.RTCxxBridge.mm
RTCxxBridge is the core of React Native initialization, and I looked at some material, and it seems that RTCxxBridge used to be called RCTBatchedBridge, so it's OK to crudely treat these two classes as the same thing.
Since the start method of RTCxxBridge is called in RCTBridge, let's see what we do from the start method.
// RTCxxBridge.mm
- (void)start {
// 1. Initialize JSThread. All subsequent JS codes are executed in this thread.
_jsThread = [[NSThread alloc] initWithTarget:[self class] selectorselector(runRunLoop) object:nil];
[_jsThread start];
// Creating a Parallel Queue
dispatch_group_t prepareBridge = dispatch_group_create();
// 2. Register all native modules.
[self registerExtraModules];
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGrouprepareBridge lazilyDiscovered:NO];
// 3. Initializing the JSExecutorFactory Instance
std::shared_ptr<JSExecutorFactory> executorFactory;
// 4. Initializes the underlying instance, namely, _reactInstance.
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
// 5. Loading the JS Code
dispatch_group_enter(prepareBridge);
__block NSData *sourceCode;
[self
loadSource:^(NSError *error, RCTSource *source) {
if (error) {
[weakSelf handleError:error];
}
sourceCode = source.data;
dispatch_group_leave(prepareBridge);
}
onProgress:^(RCTLoadingProgress *progressData) {
}
];
// 6. Execute JS after the native module and JS code are loaded.
dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
RCTCxxBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode sync:NO];
}
});
}
The preceding code is long, which uses some knowledge of GCD multi-threading. The process is described as follows:
Initialize the JS thread_jsThread.
Register all native modules on the main thread.
Prepare the bridge between JS and Native and the JS running environment.
Create the message queue RCTMessageThread on the JS thread and initialize _reactInstance.
Load the JS Bundle on the JS thread.
Execute the JS code after all the preceding operations are complete.
In fact, all the above six points can be drilled down, but the source code content involved in this section is enough. Interested readers can explore the source code based on the reference materials and the React Native source code.
Android source code analysis
1.MainActivity.java & MainApplication.java
Like iOS, the startup process starts with the entry file. Let's look at MainActivity.java:
MainActivity inherits from ReactActivity and ReactActivity inherits from AppCompatActivity:
// MainActivity.java
public class MainActivity extends ReactActivity {
// The returned component name is the same as the registered name of the JS portal.
@override
protected String getMainComponentName() {
return "rn_performance_demo";
}
}
Let's start with the Android entry file MainApplication.java:
// MainApplication.java
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
// Return the ReactPackage required by the app and add the modules to be loaded,
// This is where a third-party package needs to be added when a dependency package is added to a project.
@override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
return packages;
}
// JS bundle entry file. Set this parameter to index.js.
@override
protected String getJSMainModuleName() {
return "index";
}
};
@override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@override
public void onCreate() {
super.onCreate();
// SoLoader:Loading the C++ Underlying Library
SoLoader.init(this, /* native exopackage */ false);
}
}
The ReactApplication interface is simple and requires us to create a ReactNativeHost object:
public interface ReactApplication {
ReactNativeHost getReactNativeHost();
}
From the above analysis, we can see that everything points to the ReactNativeHost class. Let's take a look at it.
2.ReactNativeHost.java
The main task of ReactNativeHost is to create ReactInstanceManager.
public abstract class ReactNativeHost {
protected ReactInstanceManager createReactInstanceManager() {
ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
ReactInstanceManagerBuilder builder =
ReactInstanceManager.builder()
// Application Context
.setApplication(mApplication)
// JSMainModulePath is equivalent to the JS Bundle on the application home page. It can transfer the URL to obtain the JS Bundle from the server.
// Of course, this can be used only in dev mode.
.setJSMainModulePath(getJSMainModuleName())
// Indicates whether to enable the dev mode.
.setUseDeveloperSupport(getUseDeveloperSupport())
// Redbox callback
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.setUIImplementationProvider(getUIImplementationProvider())
.setJSIModulesPackage(getJSIModulePackage())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
// Add ReactPackage
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
// Obtaining the Loading Path of the JS Bundle
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
ReactInstanceManager reactInstanceManager = builder.build();
return reactInstanceManager;
}
}
3.ReactActivityDelegate.java
Let's go back to ReactActivity. It doesn't do anything by itself. All functions are implemented by its delegate class ReactActivityDelegate. So let's see how ReactActivityDelegate implements it.
public class ReactActivityDelegate {
protected void onCreate(Bundle savedInstanceState) {
String mainComponentName = getMainComponentName();
mReactDelegate =
new ReactDelegate(
getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) {
@override
protected ReactRootView createRootView() {
return ReactActivityDelegate.this.createRootView();
}
};
if (mMainComponentName != null) {
// Loading the app page
loadApp(mainComponentName);
}
}
protected void loadApp(String appKey) {
mReactDelegate.loadApp(appKey);
// SetContentView() method of Activity
getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}
}
OnCreate() instantiates a ReactDelegate. Let's look at its implementation.
4.ReactDelegate.java
In ReactDelegate.java, I don't see it doing two things:
Ø Create ReactRootView as the root view
Ø Start the RN application by calling getReactNativeHost().getReactInstanceManager()
public class ReactDelegate {
public void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
// Create ReactRootView as the root view
mReactRootView = createRootView();
// Starting the RN Application
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
}
}
Basic Startup Process The source code content involved in this section is here. Interested readers can explore the source code based on the reference materials and React Native source code.
Optimization Suggestions
For applications with React Native as the main body, the RN container needs to be initialized immediately after the app is started. There is no optimization idea. However, native-based hybrid development apps have the following advantages:
Since initialization takes the longest time, can we initialize it before entering the React Native container?
This method is very common because many H5 containers do the same. Before entering the WebView web page, create a WebView container pool and initialize the WebView in advance. After entering the H5 container, load data rendering to achieve the effect of opening the web page in seconds.
The concept of the RN container pool is very mysterious. It is actually a map. The key is the componentName of the RN page (that is, the app name transferred in AppRegistry.registerComponent(appName, Component)), and the value is an instantiated RCT RootView/ReactRootView.
After the app is started, it is initialized in advance. Before entering the RN container, it reads the container pool. If there is a matched container, it directly uses it. If there is no matched container, it is initialized again.
Write two simple cases. The following figure shows how to build an RN container pool for iOS.
@Property (nonatomic, strong) NSMutableDictionary<NSString *, RCTRootView *> *rootViewRool;
// Container Pool
-(NSMutableDictionary<NSString *, RCTRootView *> *)rootViewRool {
if (!_rootViewRool) {
_rootViewRool = @{}.mutableCopy;
}
return _rootViewRool;
}
// Cache RCTRootView
-(void)cacheRootViewNSString *)componentName pathNSString *)path propsNSDictionary *)props bridgeRCTBridge *)bridge {
// initialization
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:componentName
initialPropertiesrops];
// The instantiation must be loaded to the bottom of the screen. Otherwise, the view rendering cannot be triggered
[[UIApplication sharedApplication].keyWindow.rootViewController.view insertSubview:rootView atIndex:0];
rootView.frame = [UIScreen mainScreen].bounds;
// Put the cached RCTRootView into the container pool
NSString *key = [NSString stringWithFormat"%@_%@", componentName, path];
self.rootViewRool[key] = rootView;
}
// Read Container
-(RCTRootView *)getRootViewNSString *)componentName pathNSString *)path propsNSDictionary *)props bridgeRCTBridge *)bridge {
NSString *key = [NSString stringWithFormat"%@_%@", componentName, path];
RCTRootView *rootView = self.rootViewRool[key];
if (rootView) {
return rootView;
}
// Back-to-back logic
return [[RCTRootView alloc] initWithBridge:bridge moduleName:componentName initialPropertiesrops];
}
Android builds the RN container pool as follows:
private HashMap<String, ReactRootView> rootViewPool = new HashMap<>();
// Creating a Container
private ReactRootView createRootView(String componentName, String path, Bundle props, Context context) {
ReactInstanceManager bridgeInstance = ((ReactApplication) application).getReactNativeHost().getReactInstanceManager();
ReactRootView rootView = new ReactRootView(context);
if(props == null) {
props = new Bundle();
}
props.putString("path", path);
rootView.startReactApplication(bridgeInstance, componentName, props);
return rootView;
}
// Cache Container
public void cahceRootView(String componentName, String path, Bundle props, Context context) {
ReactRootView rootView = createRootView(componentName, path, props, context);
String key = componentName + "_" + path;
// Put the cached RCTRootView into the container pool.
rootViewPool.put(key, rootView);
}
// Read Container
public ReactRootView getRootView(String componentName, String path, Bundle props, Context context) {
String key = componentName + "_" + path;
ReactRootView rootView = rootViewPool.get(key);
if (rootView != null) {
rootView.setAppProperties(newProps);
rootViewPool.remove(key);
return rootView;
}
// Back-to-back logic
return createRootView(componentName, path, props, context);
}
Each RCTRootView/ReactRootView occupies a certain memory. Therefore, when to instantiate, how many containers to instantiate, how to limit the pool size, and when to clear containers need to be practiced and explored based on services.
3. Native Modules Binding
r/HuaweiDevelopers - React Native Startup Speed Optimization - Native Chapter (Including Source Code Analysis)
iOS source code analysis
The iOS Native Modules has three parts. The main part is the _initializeModules function in the middle:
// RCTCxxBridge.mm
- (void)start {
// Native modules returned by the moduleProvider in initWithBundleURL_moduleProvider_launchOptions when the RCTBridge is initialized
[self registerExtraModules];
// Registering All Custom Native Modules
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGrouprepareBridge lazilyDiscovered:NO];
// Initializes all native modules that are lazily loaded. This command is invoked only when Chrome debugging is used
[self registerExtraLazyModules];
}
Let's see what the _initializeModules function does:
// RCTCxxBridge.mm
- (NSArray<RCTModuleData *> *)_initializeModulesNSArray<Class> *)modules
withDispatchGroupdispatch_group_t)dispatchGroup
lazilyDiscoveredBOOL)lazilyDiscovered
{
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
// Modules that were pre-initialized should ideally be set up before
// bridge init has finished, otherwise the caller may try to access the
// module directly rather than via `[bridge moduleForClass:]`, which won't
// trigger the lazy initialization process. If the module cannot safely be
// set up on the current thread, it will instead be async dispatched
// to the main thread to be set up in _prepareModulesWithDispatchGroup:.
(void)[moduleData instance];
}
}
_moduleSetupComplete = YES;
[self _prepareModulesWithDispatchGroup:dispatchGroup];
}
According to the comments in _initializeModules and _prepareModulesWithDispatchGroup, the iOS initializes all Native Modules in the main thread during JS Bundle loading (in the JSThead thread).
Based on the previous source code analysis, we can see that when the React Native iOS container is initialized, all Native Modules are initialized. If there are many Native Modules, the startup time of the Android RN container is affected.
Android source code analysis
For the registration of Native Modules, the mainApplication.java entry file provides clues:
// MainApplication.java
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
Since auto link is enabled in React Native after 0.60, the installed third-party Native Modules are in PackageList. Therefore, you can obtain the modules of auto link by simply gettingPackages().
In the source code, in the ReactInstanceManager.java file, createReactContext() is run to create a ReactContext. One step is to register the registry of nativeModules.
// ReactInstanceManager.java
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
// Registering the nativeModules Registry
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
}
According to the function invoking, we trace the processPackages() function and use a for loop to add all Native Modules in mPackages to the registry:
// ReactInstanceManager.java
private NativeModuleRegistry processPackages(
ReactApplicationContext reactContext,
List<ReactPackage> packages,
boolean checkAndUpdatePackageMembership) {
// Create JavaModule Registry Builder, which creates the JavaModule registry,
// JavaModule Registry Registers all JavaModules to Catalyst Instance
NativeModuleRegistryBuilder nativeModuleRegistryBuilder =
new NativeModuleRegistryBuilder(reactContext, this);
// Locking mPackages
// The mPackages type is List<ReactPackage>, which corresponds to packages in the MainApplication.java file
synchronized (mPackages) {
for (ReactPackage reactPackage : packages) {
try {
// Loop the ReactPackage injected into the application. The process is to add the modules to the corresponding registry
processPackage(reactPackage, nativeModuleRegistryBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
NativeModuleRegistry nativeModuleRegistry;
try {
// Generating the Java Module Registry
nativeModuleRegistry = nativeModuleRegistryBuilder.build();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
}
return nativeModuleRegistry;
}
Finally, call processPackage() for real registration:
// ReactInstanceManager.java
private void processPackage(
ReactPackage reactPackage,
NativeModuleRegistryBuilder nativeModuleRegistryBuilder
) {
nativeModuleRegistryBuilder.processPackage(reactPackage);
}
As shown in the preceding process, full registration is performed when Android registers Native Modules. If there are a large number of Native Modules, the startup time of the Android RN container will be affected.
Optimization Suggestions
To be honest, full binding of Native Modules is unsolvable in the existing architecture: regardless of whether the native method is used or not, all native methods are initialized when the container is started. In the new RN architecture, TurboModules solves this problem (described in the next section of this article).
If you have to talk about optimization, you have another idea. Do you want to initialize all the native modules? Can I reduce the number of Native Modules? One step in the new architecture is Lean Core, which is to simplify the React Native core. Some functions/components (such as the WebView component) are removed from the main project of the RN and delivered to the community for maintenance. You can download and integrate them separately when you want to use them.
The main benefits of this approach are as follows:
· The core is more streamlined, and the RN maintainer has more energy to maintain main functions.
· Reduce the binding time of Native Modules and unnecessary JS loading time, and reduce the package size, which is more friendly to initialization performance. (After the RN version is upgraded to 0.62, the initialization speed is doubled, which is basically thanks to Lean Core.)
· Accelerate iteration and optimize development experience.
Now that Lean Core's work is almost complete, see the official issue discussion section for more discussion. We can enjoy Lean Core's work as long as we upgrade React Native.
4. How to optimize the startup performance of the new RN architecture
The new architecture of React Native has been skipping votes for almost two years. Every time you ask about the progress, the official response is "Don't rush, don't rush, we're doing it."
I personally looked forward to it all year last year, but didn't wait for anything, so I don't care when the RN will update to version 1.0.0. Although the RN official has been doing some work, I have to say that their new architecture still has something. I have watched all the articles and videos on the new architecture in the market, so I have an overall understanding of the new architecture.
Because the new architecture has not been officially released, there must be some differences in details. The specific implementation details will be subject to the official React Native.
JSI
The full name of JSI is JavaScript Interface, a framework written in C++ that allows JS to call native methods directly instead of communicating asynchronously through Bridge.
How do I understand how JavaScript directly invokes Native? Let's take a simple example. When an API such as setTimeout document.getElementById is invoked on a browser, Native Code is directly invoked on the JavaScript side. You can verify the function on the browser console.
For example, I executed an order:
let el = document.createElement('div')
The variable el does not hold a JS object, but an object that is instantiated in C++. For the object held by el, set the following attributes:
el.setAttribute('width', 100)
In this case, JS synchronously invokes the setWidth method in C++ to change the width of the element.
The JSI in the new architecture of React Native is used for this purpose. With the JSI, we can use JS to directly obtain the reference of C++ objects (Host Objects), control the UI, and directly invoke methods of Native Modules, saving the overhead of bridge asynchronous communication.
Let's take a small example of how Java/OC uses JSI to expose synchronous invocation methods to JS.
#pragma once
#include <string>
#include <unordered_map>
#include <jsi/jsi.h>
// SampleJSIObject inherits from HostObject and represents an object exposed to JS
// For JS, JS can directly invoke the properties and methods on the object synchronously
class JSI_EXPORT SampleJSIObject : public facebook::jsi::HostObject {
public:
// The first step
// Exposes window.__SampleJSIObject to JavaScript
// This is a static function that is generally called from ObjC/Java during application initialization
static void SampleJSIObject::install(jsi::Runtime &runtime) {
runtime.global().setProperty(
runtime,
"__sampleJSIObject",
jsi::Function::createFromHostFunction(
runtime,
jsi:ropNameID::forAscii(runtime, "__SampleJSIObject"),
1,
[binding](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) {
// Returns the content of a call to window.__SampleJSIObject
return std::make_shared<SampleJSIObject>();
}));
}
// Similar to a getter, this method is used each time the JS accesses the object. The function is similar to a wrapper
// For example, if we call window.__sampleJSIObject.method1(), this method will be called
jsi::Value TurboModule::get(jsi::Runtime& runtime, const jsi:ropNameID& propName) {
// Invoking method name
// For example, when window.__sampleJSIObject.method1() is called, propNameUtf8 is method1
std::string propNameUtf8 = propName.utf8(runtime);
return jsi::Function::createFromHostFunction(
runtime,
propName,
argCount,
[](facebook::jsi::Runtime &rt, const facebook::jsi::Value &thisVal, const facebook::jsi::Value *args, size_t count) {
if (propNameUtf8 == 'method1') {
// Function processing logic when method1 is invoked
}
});
}
std::vector<PropNameID> getPropertyNames(Runtime& rt){
}
}
The above example is short. To learn more about JSI, read the React Native JSI Challenge article or read the source code directly.
TurboModules
According to the previous source code analysis, in the current architecture, native modules are fully loaded during native initialization. As services are iterated, the number of native modules increases, which takes a longer time.
TurboModules solves this problem all at once. In the new architecture, native modules are loaded in lazy mode. That is, the loading is initialized only when you invoke the corresponding native modules. This solves the problem that initializing full loading takes a long time.
The calling path of TurboModules is as follows:
1. Use JSI to create a top-level Native Modules Proxy, which is called global.__turboModuleProxy.
2.Access a Native Module. For example, to access the SampleTurboModule, execute require('NativeSampleTurboModule') on the JavaScript side.
3.In the NativeSampleTurboModule.js file, we call TurboModuleRegistry.getEnforcing() and then call global.__turboModuleProxy("SampleTurboModule").
4.When global.__turboModuleProxy is invoked, the Native method exposed by the JSI in step 1 is invoked. In this case, the C++ layer finds the ObjC/Java implementation through the input string "SampleTurboModule". Finally, a corresponding JSI object is returned.
5.Now that we have the JSI object of SampleTurboModule, we can use JavaScript to synchronously invoke the properties and methods of the JSI object.
Through the preceding steps, we can see that with TurboModules, Native Modules are loaded only when they are invoked for the first time. This completely eliminates the time required for fully loading Native Modules during initialization of the React Native container. In addition, we can use JSI to implement synchronous invoking of JS and Native, which takes less time and improves efficiency.
Summary
This document analyzes the startup process of the existing architecture of React Native from the perspective of Native and summarizes the performance optimization points of the Native layer. Finally, we briefly introduce the new architecture of React Native. In the next article, I'll explain how to start with JavaScript and optimize React Native's startup speed.
By Halogenated Hydrocarbons
Original Link: https://segmentfault.com/a/1190000039797508

React Native Startup Speed Optimization - Native Chapter (Including Source Code Analysis) 0. React Native Startup Process React Native is a web fron

0. React Native Startup Process
React Native is a web front-end friendly hybrid development framework that can be divided into two parts at startup:
Running of Native Containers​
Running of JavaScript code​
The Native container is started in the existing architecture (the version number is less than 1.0.0). The native container can be divided into three parts:
Native container initialization​
Full binding of native modules​
Initialization of JSEngine​
After the container is initialized, the stage is handed over to JavaScript, and the process can be divided into two parts:
Loading, parsing, and execution of JavaScript code​
Construction of JS components​
Finally, the JS Thread sends the calculated layout information to the Native end, calculates the Shadow Tree, and then the UI Thread performs layout and rendering.
I have drawn a diagram of the preceding steps. The following table describes the optimization direction of each step from left to right:
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Note: During React Native initialization, multiple tasks may be executed concurrently. Therefore, the preceding figure only shows the initialization process of React Native and does not correspond to the execution sequence of the actual code.
1. Upgrade React Native
The best way to improve the performance of React Native applications is to upgrade a major version of the RN. After the app is upgraded from 0.59 to 0.62, no performance optimization is performed on the app, and the startup time is shortened by 1/2. When React Native's new architecture is released, both startup speed and rendering speed will be greatly improved.
2. Native container initialization
Container initialization must start from the app entry file. I will select some key code to sort out the initialization process.
iOS source code analysis
1.AppDelegate.m
AppDelegate.m is the entry file of the iOS. The code is simple. The main content is as follows:
Code:
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1. Initialize a method for loading jsbundle by RCTBridge.
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
// 2. Use RCTBridge to initialize an RCTRootView.
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"RN64"
initialProperties:nil];
// 3. Initializing the UIViewController
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
// 4. Assigns the value of RCTRootView to the view of UIViewController.
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
In general, looking at the entry document, it does three things:
Initialize an RCTBridge implementation method for loading jsbundle.
Use RCTBridge to initialize an RCTRootView.
Assign the value of RCTRootView to the view of UIViewController to mount the UI.
From the entry source code, we can see that all the initialization work points to RCTRootView, so let's see what RCTRootView does.
2.RCTRootView
Let's take a look at the header file of RCTRootView first. Let's just look at some of the methods we focus on:
Code:
// RCTRootView.h
@interface RCTRootView : UIView
// Initialization methods used in AppDelegate.m
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
From the header file:
RCTRootView inherits from UIView, so it is essentially a UI component;
When the RCTRootView invokes initWithBridge for initialization, an initialized RCTBridge must be transferred.
In the RCTRootView.m file, initWithBridge listens to a series of JS loading listening functions during initialization. After listening to the completion of JS Bundle file loading, it invokes AppRegistry.runApplication() in JS to start the RN application.
We find that RCTRootView.m only monitors various events of RCTBridge, but is not the core of initialization. Therefore, we need to go to the RCTBridge file.
3.RCTBridge.m
In RCTBridge.m, the initialization invoking path is long, and the full pasting source code is long. In short, the last call is (void)setUp. The core code is as follows:
Code:
- (Class)bridgeClass
{
return [RCTCxxBridge class];
}
- (void)setUp {
// Obtains the bridgeClass. The default value is RCTCxxBridge.
Class bridgeClass = self.bridgeClass;
// Initializing the RTCxxBridge
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
// Starting RTCxxBridge
[self.batchedBridge start];
}
We can see that the initialization of the RCTBridge points to the RTCxxBridge.
4.RTCxxBridge.mm
RTCxxBridge is the core of React Native initialization, and I looked at some material, and it seems that RTCxxBridge used to be called RCTBatchedBridge, so it's OK to crudely treat these two classes as the same thing.
Since the start method of RTCxxBridge is called in RCTBridge, let's see what we do from the start method.
Code:
// RTCxxBridge.mm
- (void)start {
// 1. Initialize JSThread. All subsequent JS codes are executed in this thread.
_jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
[_jsThread start];
// Creating a Parallel Queue
dispatch_group_t prepareBridge = dispatch_group_create();
// 2. Register all native modules.
[self registerExtraModules];
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
// 3. Initializing the JSExecutorFactory Instance
std::shared_ptr<JSExecutorFactory> executorFactory;
// 4. Initializes the underlying instance, namely, _reactInstance.
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
// 5. Loading the JS Code
dispatch_group_enter(prepareBridge);
__block NSData *sourceCode;
[self
loadSource:^(NSError *error, RCTSource *source) {
if (error) {
[weakSelf handleError:error];
}
sourceCode = source.data;
dispatch_group_leave(prepareBridge);
}
onProgress:^(RCTLoadingProgress *progressData) {
}
];
// 6. Execute JS after the native module and JS code are loaded.
dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
RCTCxxBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode sync:NO];
}
});
}
The preceding code is long, which uses some knowledge of GCD multi-threading. The process is described as follows:
1. Initialize the JS thread_jsThread.
2. Register all native modules on the main thread.
3. Prepare the bridge between JS and Native and the JS running environment.
4. Create the message queue RCTMessageThread on the JS thread and initialize _reactInstance.
5. Load the JS Bundle on the JS thread.
6. Execute the JS code after all the preceding operations are complete.
In fact, all the above six points can be drilled down, but the source code content involved in this section is enough. Interested readers can explore the source code based on the reference materials and the React Native source code.
Android source code analysis
1.MainActivity.java & MainApplication.java
Like iOS, the startup process starts with the entry file. Let's look at MainActivity.java:
MainActivity inherits from ReactActivity and ReactActivity inherits from AppCompatActivity:
Code:
// MainActivity.java
public class MainActivity extends ReactActivity {
// The returned component name is the same as the registered name of the JS portal.
@Override
protected String getMainComponentName() {
return "rn_performance_demo";
}
}
Let's start with the Android entry file MainApplication.java:
Code:
// MainApplication.java
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
// Return the ReactPackage required by the app and add the modules to be loaded,
// This is where a third-party package needs to be added when a dependency package is added to a project.
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
return packages;
}
// JS bundle entry file. Set this parameter to index.js.
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
// SoLoader:Loading the C++ Underlying Library
SoLoader.init(this, /* native exopackage */ false);
}
}
The ReactApplication interface is simple and requires us to create a ReactNativeHost object:
Code:
public interface ReactApplication {
ReactNativeHost getReactNativeHost();
}
From the above analysis, we can see that everything points to the ReactNativeHost class. Let's take a look at it.
2.ReactNativeHost.java
The main task of ReactNativeHost is to create ReactInstanceManager.
Code:
public abstract class ReactNativeHost {
protected ReactInstanceManager createReactInstanceManager() {
ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
ReactInstanceManagerBuilder builder =
ReactInstanceManager.builder()
// Application Context
.setApplication(mApplication)
// JSMainModulePath is equivalent to the JS Bundle on the application home page. It can transfer the URL to obtain the JS Bundle from the server.
// Of course, this can be used only in dev mode.
.setJSMainModulePath(getJSMainModuleName())
// Indicates whether to enable the dev mode.
.setUseDeveloperSupport(getUseDeveloperSupport())
// Redbox callback
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.setUIImplementationProvider(getUIImplementationProvider())
.setJSIModulesPackage(getJSIModulePackage())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
// Add ReactPackage
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
// Obtaining the Loading Path of the JS Bundle
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
ReactInstanceManager reactInstanceManager = builder.build();
return reactInstanceManager;
}
}
3.ReactActivityDelegate.java
Let's go back to ReactActivity. It doesn't do anything by itself. All functions are implemented by its delegate class ReactActivityDelegate. So let's see how ReactActivityDelegate implements it.
Code:
public class ReactActivityDelegate {
protected void onCreate(Bundle savedInstanceState) {
String mainComponentName = getMainComponentName();
mReactDelegate =
new ReactDelegate(
getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) {
@Override
protected ReactRootView createRootView() {
return ReactActivityDelegate.this.createRootView();
}
};
if (mMainComponentName != null) {
// Loading the app page
loadApp(mainComponentName);
}
}
protected void loadApp(String appKey) {
mReactDelegate.loadApp(appKey);
// SetContentView() method of Activity
getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}
}
OnCreate() instantiates a ReactDelegate. Let's look at its implementation.
4.ReactDelegate.java
In ReactDelegate.java, I don't see it doing two things:
Create ReactRootView as the root view​
Start the RN application by calling getReactNativeHost().getReactInstanceManager()​
Code:
public class ReactDelegate {
public void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
// Create ReactRootView as the root view
mReactRootView = createRootView();
// Starting the RN Application
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
}
}
Basic Startup Process The source code content involved in this section is here. Interested readers can explore the source code based on the reference materials and React Native source code.
Optimization Suggestions
For applications with React Native as the main body, the RN container needs to be initialized immediately after the app is started. There is no optimization idea. However, native-based hybrid development apps have the following advantages:
Since initialization takes the longest time, can we initialize it before entering the React Native container?
This method is very common because many H5 containers do the same. Before entering the WebView web page, create a WebView container pool and initialize the WebView in advance. After entering the H5 container, load data rendering to achieve the effect of opening the web page in seconds.
The concept of the RN container pool is very mysterious. It is actually a map. The key is the componentName of the RN page (that is, the app name transferred in AppRegistry.registerComponent(appName, Component)), and the value is an instantiated RCT RootView/ReactRootView.
After the app is started, it is initialized in advance. Before entering the RN container, it reads the container pool. If there is a matched container, it directly uses it. If there is no matched container, it is initialized again.
Write two simple cases. The following figure shows how to build an RN container pool for iOS.
Code:
@property (nonatomic, strong) NSMutableDictionary<NSString *, RCTRootView *> *rootViewRool;
// Container Pool
-(NSMutableDictionary<NSString *, RCTRootView *> *)rootViewRool {
if (!_rootViewRool) {
_rootViewRool = @{}.mutableCopy;
}
return _rootViewRool;
}
// Cache RCTRootView
-(void)cacheRootView:(NSString *)componentName path:(NSString *)path props:(NSDictionary *)props bridge:(RCTBridge *)bridge {
// initialization
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:componentName
initialProperties:props];
// The instantiation must be loaded to the bottom of the screen. Otherwise, the view rendering cannot be triggered
[[UIApplication sharedApplication].keyWindow.rootViewController.view insertSubview:rootView atIndex:0];
rootView.frame = [UIScreen mainScreen].bounds;
// Put the cached RCTRootView into the container pool
NSString *key = [NSString stringWithFormat:@"%@_%@", componentName, path];
self.rootViewRool[key] = rootView;
}
// Read Container
-(RCTRootView *)getRootView:(NSString *)componentName path:(NSString *)path props:(NSDictionary *)props bridge:(RCTBridge *)bridge {
NSString *key = [NSString stringWithFormat:@"%@_%@", componentName, path];
RCTRootView *rootView = self.rootViewRool[key];
if (rootView) {
return rootView;
}
// Back-to-back logic
return [[RCTRootView alloc] initWithBridge:bridge moduleName:componentName initialProperties:props];
}
Android builds the RN container pool as follows:
Code:
private HashMap<String, ReactRootView> rootViewPool = new HashMap<>();
// Creating a Container
private ReactRootView createRootView(String componentName, String path, Bundle props, Context context) {
ReactInstanceManager bridgeInstance = ((ReactApplication) application).getReactNativeHost().getReactInstanceManager();
ReactRootView rootView = new ReactRootView(context);
if(props == null) {
props = new Bundle();
}
props.putString("path", path);
rootView.startReactApplication(bridgeInstance, componentName, props);
return rootView;
}
// Cache Container
public void cahceRootView(String componentName, String path, Bundle props, Context context) {
ReactRootView rootView = createRootView(componentName, path, props, context);
String key = componentName + "_" + path;
// Put the cached RCTRootView into the container pool.
rootViewPool.put(key, rootView);
}
// Read Container
public ReactRootView getRootView(String componentName, String path, Bundle props, Context context) {
String key = componentName + "_" + path;
ReactRootView rootView = rootViewPool.get(key);
if (rootView != null) {
rootView.setAppProperties(newProps);
rootViewPool.remove(key);
return rootView;
}
// Back-to-back logic
return createRootView(componentName, path, props, context);
}
Each RCTRootView/ReactRootView occupies a certain memory. Therefore, when to instantiate, how many containers to instantiate, how to limit the pool size, and when to clear containers need to be practiced and explored based on services.
3. Native Modules Binding
iOS source code analysis
The iOS Native Modules has three parts. The main part is the _initializeModules function in the middle:
Code:
// RCTCxxBridge.mm
- (void)start {
// Native modules returned by the moduleProvider in initWithBundleURL_moduleProvider_launchOptions when the RCTBridge is initialized
[self registerExtraModules];
// Registering All Custom Native Modules
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
// Initializes all native modules that are lazily loaded. This command is invoked only when Chrome debugging is used
[self registerExtraLazyModules];
}
Let's see what the _initializeModules function does:
Code:
// RCTCxxBridge.mm
- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<Class> *)modules
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
{
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
// Modules that were pre-initialized should ideally be set up before
// bridge init has finished, otherwise the caller may try to access the
// module directly rather than via `[bridge moduleForClass:]`, which won't
// trigger the lazy initialization process. If the module cannot safely be
// set up on the current thread, it will instead be async dispatched
// to the main thread to be set up in _prepareModulesWithDispatchGroup:.
(void)[moduleData instance];
}
}
_moduleSetupComplete = YES;
[self _prepareModulesWithDispatchGroup:dispatchGroup];
}
According to the comments in _initializeModules and _prepareModulesWithDispatchGroup, the iOS initializes all Native Modules in the main thread during JS Bundle loading (in the JSThead thread).
Based on the previous source code analysis, we can see that when the React Native iOS container is initialized, all Native Modules are initialized. If there are many Native Modules, the startup time of the Android RN container is affected.
Android source code analysis
For the registration of Native Modules, the mainApplication.java entry file provides clues:
Code:
// RCTCxxBridge.mm
- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<Class> *)modules
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
{
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
// Modules that were pre-initialized should ideally be set up before
// bridge init has finished, otherwise the caller may try to access the
// module directly rather than via `[bridge moduleForClass:]`, which won't
// trigger the lazy initialization process. If the module cannot safely be
// set up on the current thread, it will instead be async dispatched
// to the main thread to be set up in _prepareModulesWithDispatchGroup:.
(void)[moduleData instance];
}
}
_moduleSetupComplete = YES;
[self _prepareModulesWithDispatchGroup:dispatchGroup];
}
Since auto link is enabled in React Native after 0.60, the installed third-party Native Modules are in PackageList. Therefore, you can obtain the modules of auto link by simply gettingPackages().
In the source code, in the ReactInstanceManager.java file, createReactContext() is run to create a ReactContext. One step is to register the registry of nativeModules.
Code:
// ReactInstanceManager.java
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
// Registering the nativeModules Registry
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
}
According to the function invoking, we trace the processPackages() function and use a for loop to add all Native Modules in mPackages to the registry:
Code:
// ReactInstanceManager.java
private NativeModuleRegistry processPackages(
ReactApplicationContext reactContext,
List<ReactPackage> packages,
boolean checkAndUpdatePackageMembership) {
// Create JavaModule Registry Builder, which creates the JavaModule registry,
// JavaModule Registry Registers all JavaModules to Catalyst Instance
NativeModuleRegistryBuilder nativeModuleRegistryBuilder =
new NativeModuleRegistryBuilder(reactContext, this);
// Locking mPackages
// The mPackages type is List<ReactPackage>, which corresponds to packages in the MainApplication.java file
synchronized (mPackages) {
for (ReactPackage reactPackage : packages) {
try {
// Loop the ReactPackage injected into the application. The process is to add the modules to the corresponding registry
processPackage(reactPackage, nativeModuleRegistryBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
NativeModuleRegistry nativeModuleRegistry;
try {
// Generating the Java Module Registry
nativeModuleRegistry = nativeModuleRegistryBuilder.build();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
}
return nativeModuleRegistry;
}
Finally, call processPackage() for real registration:
Code:
// ReactInstanceManager.java
private void processPackage(
ReactPackage reactPackage,
NativeModuleRegistryBuilder nativeModuleRegistryBuilder
) {
nativeModuleRegistryBuilder.processPackage(reactPackage);
}
As shown in the preceding process, full registration is performed when Android registers Native Modules. If there are a large number of Native Modules, the startup time of the Android RN container will be affected.
Optimization Suggestions
To be honest, full binding of Native Modules is unsolvable in the existing architecture: regardless of whether the native method is used or not, all native methods are initialized when the container is started. In the new RN architecture, TurboModules solves this problem (described in the next section of this article).
If you have to talk about optimization, you have another idea. Do you want to initialize all the native modules? Can I reduce the number of Native Modules? One step in the new architecture is Lean Core, which is to simplify the React Native core. Some functions/components (such as the WebView component) are removed from the main project of the RN and delivered to the community for maintenance. You can download and integrate them separately when you want to use them.
The main benefits of this approach are as follows:
The core is more streamlined, and the RN maintainer has more energy to maintain main functions.
Reduce the binding time of Native Modules and unnecessary JS loading time, and reduce the package size, which is more friendly to initialization performance. (After the RN version is upgraded to 0.62, the initialization speed is doubled, which is basically thanks to Lean Core.)
Accelerate iteration and optimize development experience.
Now that Lean Core's work is almost complete, see the official issue discussion section for more discussion. We can enjoy Lean Core's work as long as we upgrade React Native.
4. How to optimize the startup performance of the new RN architecture
The new architecture of React Native has been skipping votes for almost two years. Every time you ask about the progress, the official response is "Don't rush, don't rush, we're doing it."
​I personally looked forward to it all year last year, but didn't wait for anything, so I don't care when the RN will update to version 1.0.0. Although the RN official has been doing some work, I have to say that their new architecture still has something. I have watched all the articles and videos on the new architecture in the market, so I have an overall understanding of the new architecture.
Because the new architecture has not been officially released, there must be some differences in details. The specific implementation details will be subject to the official React Native.
JSI
The full name of JSI is JavaScript Interface, a framework written in C++ that allows JS to call native methods directly instead of communicating asynchronously through Bridge.
How do I understand how JavaScript directly invokes Native? Let's take a simple example. When an API such as setTimeout document.getElementById is invoked on a browser, Native Code is directly invoked on the JavaScript side. You can verify the function on the browser console.
For example, I executed an order:
Code:
let el = document.createElement('div')
The variable el does not hold a JS object, but an object that is instantiated in C++. For the object held by el, set the following attributes:
Code:
el.setAttribute('width', 100)
In this case, JS synchronously invokes the setWidth method in C++ to change the width of the element.
The JSI in the new architecture of React Native is used for this purpose. With the JSI, we can use JS to directly obtain the reference of C++ objects (Host Objects), control the UI, and directly invoke methods of Native Modules, saving the overhead of bridge asynchronous communication.
Let's take a small example of how Java/OC uses JSI to expose synchronous invocation methods to JS.
Code:
#pragma once
#include <string>
#include <unordered_map>
#include <jsi/jsi.h>
// SampleJSIObject inherits from HostObject and represents an object exposed to JS
// For JS, JS can directly invoke the properties and methods on the object synchronously
class JSI_EXPORT SampleJSIObject : public facebook::jsi::HostObject {
public:
// The first step
// Exposes window.__SampleJSIObject to JavaScript
// This is a static function that is generally called from ObjC/Java during application initialization
static void SampleJSIObject::install(jsi::Runtime &runtime) {
runtime.global().setProperty(
runtime,
"__sampleJSIObject",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__SampleJSIObject"),
1,
[binding](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count) {
// Returns the content of a call to window.__SampleJSIObject
return std::make_shared<SampleJSIObject>();
}));
}
// Similar to a getter, this method is used each time the JS accesses the object. The function is similar to a wrapper
// For example, if we call window.__sampleJSIObject.method1(), this method will be called
jsi::Value TurboModule::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
// Invoking method name
// For example, when window.__sampleJSIObject.method1() is called, propNameUtf8 is method1
std::string propNameUtf8 = propName.utf8(runtime);
return jsi::Function::createFromHostFunction(
runtime,
propName,
argCount,
[](facebook::jsi::Runtime &rt, const facebook::jsi::Value &thisVal, const facebook::jsi::Value *args, size_t count) {
if (propNameUtf8 == 'method1') {
// Function processing logic when method1 is invoked
}
});
}
std::vector<PropNameID> getPropertyNames(Runtime& rt){
}
}
The above example is short. To learn more about JSI, read the React Native JSI Challenge article or read the source code directly.
TurboModules
According to the previous source code analysis, in the current architecture, native modules are fully loaded during native initialization. As services are iterated, the number of native modules increases, which takes a longer time.
TurboModules solves this problem all at once. In the new architecture, native modules are loaded in lazy mode. That is, the loading is initialized only when you invoke the corresponding native modules. This solves the problem that initializing full loading takes a long time.
The calling path of TurboModules is as follows:
Use JSI to create a top-level Native Modules Proxy, which is called global.__turboModuleProxy.
Access a Native Module. For example, to access the SampleTurboModule, execute require('NativeSampleTurboModule') on the JavaScript side.
In the NativeSampleTurboModule.js file, we call TurboModuleRegistry.getEnforcing() and then call global.__turboModuleProxy("SampleTurboModule").
When global.__turboModuleProxy is invoked, the Native method exposed by the JSI in step 1 is invoked. In this case, the C++ layer finds the ObjC/Java implementation through the input string "SampleTurboModule". Finally, a corresponding JSI object is returned.
Now that we have the JSI object of SampleTurboModule, we can use JavaScript to synchronously invoke the properties and methods of the JSI object.
Through the preceding steps, we can see that with TurboModules, Native Modules are loaded only when they are invoked for the first time. This completely eliminates the time required for fully loading Native Modules during initialization of the React Native container. In addition, we can use JSI to implement synchronous invoking of JS and Native, which takes less time and improves efficiency.
Summary
This document analyzes the startup process of the existing architecture of React Native from the perspective of Native and summarizes the performance optimization points of the Native layer. Finally, we briefly introduce the new architecture of React Native. In the next article, I'll explain how to start with JavaScript and optimize React Native's startup speed.
By Halogenated Hydrocarbons
Original Link: https://segmentfault.com/a/1190000039797508
is RN container same as google map?

Writing a Serverless Android app (ft. Huawei's AppGallery Connect)

Part 0 - Why?
Part 1 - Auth
Part 2 - CloudDB
Part 3 - More Cloud
Part 4 - Login and Register
Over the next couple of months I will be releasing a complete end to end guide to creating an Android app using serverless functionality to completely remove the need for any backend server/hosting etc.
These guides will be made up of a weekly live stream which will then be edited into a YouTube video along with each having a blog post for those that prefer a written guide!
But before we get into the actual guide (which will start with 'Part 1' next week) lets start from the... well start! Why might someone want to build a serverless app? what IS a serverless app? and what does Huawei's AppGallery Connect have to do with it?
Well I'm glad you asked!
What is a serverless app?​If Cloud computing takes away the need to manage the hardware, serverless computing takes away the need to manage the software. Its an extension of cloud computing where the provider handles everything about the servers and simply provides some kind of interface for the user to access their services. This might be in the form of an API, SDK, GUI or all of the above!
Why would I want to build a serverless app?​Server management is in its own right a full time job, from setting up the environment to installing and managing the software stack. Security updates, security hardening, authentication (to name a few) are all things that need to be managed in a traditional backend server setup. By using a serverless service all of this management work is removed, you as the app developer simply have access to the resources you need when you need them.
A couple of examples of why or when you might use a serverless system:
Example one - Prototyping​If you need to build an application prototype quickly, you want something that is just going to work and don't want the hassle of setting everything up! By using a serverless system you have instant access to the services you need, this lets you focus on prototyping the app itself.
Example two - Basic requirements​Many apps have very basic backend server requirements. Perhaps they just want to store users details and setting preferences. Or maybe they just need a way to host and download files. These kinds of requirements tend to mean that a full managed backend server is overkill. When your requirements are simple no one wants to spend hours setting it up and managing a server!
Example Three - Small Team/Limited knowledge​If your a small team (or solo) you might simply not have the knowledge or man power to manage servers. The time taken to learn and maintain that knowledge might significantly impact the amount of time you have to develop your application. Sometimes its just much more cost effective to let another company manage this.
What does Huawei's AppGallery Connect have to do with it?​As part of Huawei's AppGallery platform they now offer a wide range of serverless features and functionality. These services come under the AppGallery Connect suite, including but not limited to, database, web hosting, authentication and storage. These services include generous free tiers which make prototyping and developing using these services even more attractive and cost effective.
Because of this we will be using this platform throughout the development guides as we explore what can be done with a serverless Android app!
Part 1 - Auth​
This weeks Video is below
But for those that would rather a written guide, lets get into it!
Project Setup​Starting with a brand new project (or one that has never used Huawei services) we will need to start by setting up a new app. (If you haven't setup your developer account yet sign up!)
If you would like the complete step by step guide on how to get setup check out the Official Documentation, but below is a summery.
Navigate to the the AppGallery Connect area of the developer portal, here you need to create a new project.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Follow the new project guide, giving that project a name. Once completed on the left hand menu find the Auth service under the build menu.
Click the enable now button in the top right corner to enable this service for your project. Set a default data processing location, depending on your physical location will most likely help decide which to use. For us we have selected Germany as this is the closest location and within the EU.
From here you are presented with the list of authentication modes, Huawei supports a wide range of services from Facebook login to AppleID. However today we are focusing on the email auth method. Click the enable button next to this.
Now that everything is setup in the project, its time to setup the app! At this point I will assume you have created a new blank project in Android Studio and given it a package name etc.
From the project settings top screen, select the Add app button to setup a new app under this project.
Fill in the add app form, setting the platform, app name, package etc so that it can be added to the created project. Its worth noting at this point, that while we are focusing on Android today many of these services can be used across multiple platforms including iOS and web.
Once completed you will be told to download the agconnect-services.json file and presented with a code to get the core services setup. As we are already focusing on a specific service for today the setup at this point is a little different.
Start by placing your nearly downloaded agconnect-services.json file into the app directory of your project.
Next we will get gradle configured correctly. In your top level gradle build file add the below to your repositories both under buildscript and allprojects
Code:
maven {
url 'https://developer.huawei.com/repo/'
}
And to your dependencies add classpath 'com.huawei.agconnect:agcp:1.4.1.300'
Your file should now look a little like
Code:
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
google()
mavenCentral()
maven {
url 'https://developer.huawei.com/repo/'
}
}
dependencies {
classpath "com.android.tools.build:gradle:4.2.2"
classpath 'com.huawei.agconnect:agcp:1.4.1.300'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
}
allprojects {
repositories {
google()
mavenCentral()
jcenter() // Warning: this repository is going to shut down soon
maven {
url 'https://developer.huawei.com/repo/'
}
}
}
task clean(type: Delete) {
delete rootProject.buildDir
}
Next open your app build.gradle file
Start by adding the agconnect plugin into your plugins list
Code:
plugins {
id 'com.android.application'
id 'com.huawei.agconnect'
}
Make sure to set your midSdkVersion to at least `17` (this is required for any Huawei AppGallery Connect services.
And then in dependencies we are going to add the core and auth services
Code:
implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
So your file should now look something like
Code:
plugins {
id 'com.android.application'
id 'com.huawei.agconnect'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "site.zpweb.barker"
minSdkVersion 17
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
And thats it! let gradle sync up and we are all good to go!
Registration​As I mentioned before there are a wide range of ways a user can authenticate using the Auth service, but here we are looking at the email service. Email authenticate the user by sending them a code via (your guessed it) Email. The user then inputs this code back into the app to confirm that they are who the say they are, and that they have access to that contact method. We will assume you have setup some kind of register view that will capture a users email.
We start by requesting an authentication code be set to the user, the code to do this looks like
Java:
VerifyCodeSettings settings = VerifyCodeSettings.newBuilder()
.action(VerifyCodeSettings.ACTION_REGISTER_LOGIN)
.sendInterval(30)
.locale(Locale.ENGLISH)
.build();
Task<VerifyCodeResult> task = EmailAuthProvider.requestVerifyCode(emailString, settings);
task.addOnSuccessListener(TaskExecutors.uiThread(), new OnSuccessListener<VerifyCodeResult>() {
@Override
public void onSuccess(VerifyCodeResult verifyCodeResult) {
authCodeDialog();
}
}).addOnFailureListener(TaskExecutors.uiThread(), new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(RegisterActivity.this,
"Error, code sending failed: " + e,
Toast.LENGTH_LONG).show();
}
});
Lets break this down, we start by defining a `VerifyCodeSettings` object, this holds all the settings relating to the sending of the code. Here we define what locale should be used for the message text that is sent, what kind of code it is and the send interval.
Next we create a task to be run
Java:
Task<VerifyCodeResult> task = EmailAuthProvider.requestVerifyCode(emailString, settings);
Using the EmailAuthProvider, where `emailString` is the email address the user has entered, as a string and the settings object is the VerifyCodeSettings object we just created.
Next we setup an OnSucessListener which will be called if the code was successfully sent to the user. In this example we are calling the method `authCodeDialog();` to display a dialog to enter the code, which will see in a moment.
We also setup an OnFailureListener which simply create a toast on screen to display what ever error is sent back.
Now that we have sent the user a code we should display some view for them to enter that code, in my instance I have the method below, but of course this could be what ever view you wanted.
Java:
private void authCodeDialog() {
AlertDialog.Builder alert = new AlertDialog.Builder(this);
final EditText authCodeField = new EditText(this);
alert.setMessage("Enter your auth code below");
alert.setTitle("Authentication Code");
alert.setView(authCodeField);
alert.setPositiveButton("Register", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
String authCode = authCodeField.getText().toString();
register(authCode);
}
});
alert.setNegativeButton("Cancel", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
Toast.makeText(RegisterActivity.this,
"Registration Cancelled",
Toast.LENGTH_LONG).show();
}
});
alert.show();
}
Finally once the user has inputted the code we can register them as you can see above we have a register method that is called, this has the below code:
Java:
EmailUser emailUser = new EmailUser.Builder()
.setEmail(emailString)
.setVerifyCode(authCode)
.build();
AGConnectAuth.getInstance().createUser(emailUser).addOnSuccessListener(new OnSuccessListener<SignInResult>() {
@Override
public void onSuccess(SignInResult signInResult) {
Toast.makeText(RegisterActivity.this,
"Register Successful: " + signInResult.getUser().getUid(),
Toast.LENGTH_LONG);
}
}).addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(RegisterActivity.this,
"Registering failed " + e,
Toast.LENGTH_LONG);
}
});
So we start by creating an `EmailUser` using the email address we captured earlier and the authCode that the user has entered. Using that `EmailUser` we then attempt to register. Note that we can also set a password against the `EmailUser`, if we do this when they go to login they do not need to verify their email address again. They can just enter their email and password.
As you can see we pass the created object into the createUser method, attaching OnSuccess and OnFailure Listeners. If the user is successfully registered, i.e the code they entered matches what was sent we are returned a `SignInResult` this object containes the user. So in this example we simply print out the registered users UID to confirm it completed successfully.
Login​
Now that we have covered the sign up process lets look at how we might handle a login process. This would assume that the user has already registered via the above method and is now logging into the app, perhaps after installing it on a new phone.
As I mentioned before if the user signed up without a password then start by sending a verify code, in just the same way as we did during the sign up process. Once we have the code we can generate a `AGCOnnectAuthCredential` object
Java:
AGConnectAuthCredential credential = credential = EmailAuthProvider.credentialWithVerifyCode(
email.getText().toString().trim(),
null,
authCode);
Here we get the email that the user entered (`email` being an `EditText` object). We pass null for the password as we haven't used this, and finally the authCode which the user has entered into the app.
Now we can attempt the sign the user in:
Java:
AGConnectAuth.getInstance().signIn(credential)
.addOnSuccessListener(new OnSuccessListener<SignInResult>() {
@Override
public void onSuccess(SignInResult signInResult) {
Toast.makeText(MainActivity.this, "Sign in successful: " +
signInResult.getUser().getUid(), Toast.LENGTH_LONG);
}
})
.addOnFailureListener(new OnFailureListener() {
@Override
public void onFailure(Exception e) {
Toast.makeText(MainActivity.this, "sign in failed:" + e, Toast.LENGTH_LONG);
}
});
We pass the credential object into the signIn method, if the code and email matched and was correct the OnSuccessListener will return the SignInResult object just as it did during the sign up, from here we have access to the users detials.
If for any reason the login fails we will get an Exception in the OnFailureListener.
And thats it! We have setup the application to use Huawei services and configured the app to use email authentication during sign up and log in.
For more information on the Auth service, full documentation can be found here https://developer.huawei.com/consum...Guides/agc-auth-introduction-0000001053732605
We will be back with the next part next week!
Thanks for sharing!!
Except login feature, how will we store huge amount of data?
ask011 said:
Except login feature, how will we store huge amount of data?
Click to expand...
Click to collapse
CloudDB and Cloud Storage are another two services offered which will allow you to store any data you need to! We will be looking at this in the coming weeks so stay tuned!
Part 2 - CloudDB​
This weeks video
Starting with the project as we completed last week (on GitHub) lets now configure the application to support and use the CloudDB functionality from Huawei. Today we will be setting up everything we need to be able to use the CloudDB service and set/get/delete data.
Navigate to the the AppGallery Connect area of the developer portal, select the project we setup last week and on the left hand menu find CloudDB under the build sub menu.
From here enable the service and if you haven't already you will be asked to setup a data location.
Next under the ObjectTypes tab lets create the first data object, click add and you will be presented with a screen like this:
For todays example set your object type name to User, then we will create three fields, id, uid and username as below
Finally create an index called user_id with index field set to id. Leave data permission as they are and save your new data object.
Next go over to the next tab "Cloud DB Zones" and create a new zone, for this example we will call it "Barker".
Head back over to the ObjectTypes tab and press the "Export" button, Pick the JAVA file format, and android for file type. Then enter your Android package name.
This will download two files in a zipped folder, unzip and add these java files to your Android project.
Lets now take a look at these files, if you have followed my naming schemes your User.java should look like:
Java:
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
* Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
*/
package site.zpweb.barker.model;
import com.huawei.agconnect.cloud.database.CloudDBZoneObject;
import com.huawei.agconnect.cloud.database.Text;
import com.huawei.agconnect.cloud.database.annotations.DefaultValue;
import com.huawei.agconnect.cloud.database.annotations.EntireEncrypted;
import com.huawei.agconnect.cloud.database.annotations.NotNull;
import com.huawei.agconnect.cloud.database.annotations.Indexes;
import com.huawei.agconnect.cloud.database.annotations.PrimaryKeys;
import java.util.Date;
/**
* Definition of ObjectType User.
*
* @since 2021-07-09
*/
@PrimaryKeys({"id"})
@Indexes({"user_id:id"})
public final class User extends CloudDBZoneObject {
private Integer id;
@DefaultValue(stringValue = "0")
private String uid;
@DefaultValue(stringValue = "0")
private String username;
public User() {
super(User.class);
this.uid = "0";
this.username = "0";
}
public void setId(Integer id) {
this.id = id;
}
public Integer getId() {
return id;
}
public void setUid(String uid) {
this.uid = uid;
}
public String getUid() {
return uid;
}
public void setUsername(String username) {
this.username = username;
}
public String getUsername() {
return username;
}
}
Which as you can see is a fairly standard object class with setup for all the fields we defined in the ObjectType.
The other generated file ObjectTypeInfoHelper.java should look like
Java:
/*
* Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
* Generated by the CloudDB ObjectType compiler. DO NOT EDIT!
*/
package site.zpweb.barker.model;
import com.huawei.agconnect.cloud.database.CloudDBZoneObject;
import com.huawei.agconnect.cloud.database.ObjectTypeInfo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* Definition of ObjectType Helper.
*
* @since 2021-07-09
*/
public final class ObjectTypeInfoHelper {
private static final int FORMAT_VERSION = 2;
private static final int OBJECT_TYPE_VERSION = 10;
public static ObjectTypeInfo getObjectTypeInfo() {
ObjectTypeInfo objectTypeInfo = new ObjectTypeInfo();
objectTypeInfo.setFormatVersion(FORMAT_VERSION);
objectTypeInfo.setObjectTypeVersion(OBJECT_TYPE_VERSION);
List<Class<? extends CloudDBZoneObject>> objectTypeList = new ArrayList<>();
Collections.addAll(objectTypeList, User.class);
objectTypeInfo.setObjectTypes(objectTypeList);
return objectTypeInfo;
}
}
Which is a helper class used by the framework to know what Object classes are available, in this instance just the User class.
You will notice that at this point the code doesn't compile! We need to add in the new CloudDB dependency to the apps build.gradle file
Code:
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.4.8.300'
So your gradle file should now look like:
Code:
plugins {
id 'com.android.application'
id 'com.huawei.agconnect'
}
android {
compileSdkVersion 30
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "site.zpweb.barker"
minSdkVersion 17
targetSdkVersion 30
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation 'com.huawei.agconnect:agconnect-core:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
implementation 'com.huawei.agconnect:agconnect-cloud-database:1.4.8.300'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.4.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}
Once gradle has synced up we are good to go!
To more easily manage the connection between the CloudDB and functionality within the app I suggest written a separate class to do this. Below is my CloudDBManager class which will act as a wrapper handling much of the CloudDB functionality.
Java:
package site.zpweb.barker.db;
import android.content.Context;
import com.huawei.agconnect.cloud.database.AGConnectCloudDB;
import com.huawei.agconnect.cloud.database.CloudDBZone;
import com.huawei.agconnect.cloud.database.CloudDBZoneConfig;
import com.huawei.agconnect.cloud.database.CloudDBZoneObjectList;
import com.huawei.agconnect.cloud.database.CloudDBZoneQuery;
import com.huawei.agconnect.cloud.database.CloudDBZoneSnapshot;
import com.huawei.agconnect.cloud.database.exceptions.AGConnectCloudDBException;
import com.huawei.hmf.tasks.OnFailureListener;
import com.huawei.hmf.tasks.OnSuccessListener;
import com.huawei.hmf.tasks.Task;
import java.util.ArrayList;
import java.util.List;
import site.zpweb.barker.model.User;
import site.zpweb.barker.utils.Toaster;
public class CloudDBManager {
private int maxUserID = 0;
Toaster toaster = new Toaster();
private final AGConnectCloudDB cloudDB;
private CloudDBZone cloudDBZone;
public CloudDBManager(){
cloudDB = AGConnectCloudDB.getInstance();
}
public static void initCloudDB(Context context){
AGConnectCloudDB.initialize(context);
}
public void openCloudDBZone(Context context){
CloudDBZoneConfig config = new CloudDBZoneConfig("Barker",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
config.setPersistenceEnabled(true);
try {
cloudDBZone = cloudDB.openCloudDBZone(config, true);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
public void closeCloudDBZone(Context context){
try {
cloudDB.closeCloudDBZone(cloudDBZone);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
public void upsertUser(User user, Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(user);
executeTask(upsertTask, context);
}
public void upsertUsers(List<User> users,Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(users);
executeTask(upsertTask, context);
}
private void executeTask(Task<Integer> task,Context context) {
task.addOnSuccessListener(integer -> toaster.sendSuccessToast(context, "upsert successful"))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
public void deleteUser(User user){
cloudDBZone.executeDelete(user);
}
public int getMaxUserID(){
return maxUserID;
}
private void updateMaxUserID(User user){
if (maxUserID < user.getId()) {
maxUserID = user.getId();
}
}
public void getAllUsers(Context context){
queryUsers(CloudDBZoneQuery.where(User.class), context);
}
public void queryUsers(CloudDBZoneQuery<User> query, Context context) {
Task<CloudDBZoneSnapshot<User>> task = cloudDBZone.executeQuery(query,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
task.addOnSuccessListener(userCloudDBZoneSnapshot -> processResults(userCloudDBZoneSnapshot, context))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
private void processResults(CloudDBZoneSnapshot<User> userCloudDBZoneSnapshot, Context context) {
CloudDBZoneObjectList<User> userCursor = userCloudDBZoneSnapshot.getSnapshotObjects();
List<User> userList = new ArrayList<>();
try {
while (userCursor.hasNext()) {
User user = userCursor.next();
updateMaxUserID(user);
userList.add(user);
}
//HAVE USER LIST
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
} finally {
userCloudDBZoneSnapshot.release();
}
}
}
Lets break it down and take a look at what we are going to be able to do with this class.
First we define the variables we are going to need, the most important thing here is maxUserID. CloudDB currently doesn't have any auto increment support so we will need to keep a running check on what is the last used ID.
Next we have the class constructor where we will get an instance of the CloudDB interface to be used by the other methods in this class.
Java:
public CloudDBManager(){
cloudDB = AGConnectCloudDB.getInstance();
}
Next up with a static init method, the CloudDB initialize method must be called at the start of your application, so this static method is used to do just that!
Java:
public static void initCloudDB(Context context){
AGConnectCloudDB.initialize(context);
}
In the openCloudDBZone method we setup the configured cloud zone, this is where the data will be saved to and received from. Note that you could have multiple zone's that all use the same ObjectType's. They wouldn't however have access to other zone's data. Useful if you have multiple applications that require similar data structures.
Java:
public void openCloudDBZone(Context context){
config = new CloudDBZoneConfig("Barker",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
config.setPersistenceEnabled(true);
try {
cloudDBZone = cloudDB.openCloudDBZone(config, true);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
As we have an open method we should also have a close method to shut down the apps access to that zone.
Java:
public void closeCloudDBZone(Context context){
try {
cloudDB.closeCloudDBZone(cloudDBZone);
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
Next we have three methods that handle the upsert of User's, that is either the update or insert depending on if the user already exists in the database. The executeTask method sets the success/failure listeners while the other two methods simply set up the task depending on if we are upserting one user or a list of users.
Java:
public void upsertUser(User user, Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(user);
executeTask(upsertTask, context);
}
public void upsertUsers(List<User> users,Context context) {
Task<Integer> upsertTask = cloudDBZone.executeUpsert(users);
executeTask(upsertTask, context);
}
private void executeTask(Task<Integer> task,Context context) {
task.addOnSuccessListener(integer -> toaster.sendSuccessToast(context, "upsert successful"))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
Next we have a simple method that will delete a User from the database
Java:
public void deleteUser(User user){
cloudDBZone.executeDelete(user);
}
Then a getter for the maxUserID, and a method to update the maxUserID. If the given User has a greater ID than the current max, update the max ID to that Users ID.
Java:
public int getMaxUserID(){
return maxUserID;
}
private void updateMaxUserID(User user){
if (maxUserID < user.getId()) {
maxUserID = user.getId();
}
}
And finally we have three methods that handle the querying of data, the getAllUsers method makes use of a predefined query which simply asks for all objects that are of the type User.
Java:
public void getAllUsers(Context context){
queryUsers(CloudDBZoneQuery.where(User.class), context);
}
Next the queryUsers method which will generate the query task and runs it, on success we then pass the result into processResult.
Java:
public void queryUsers(CloudDBZoneQuery<User> query, Context context) {
Task<CloudDBZoneSnapshot<User>> task = cloudDBZone.executeQuery(query,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY);
task.addOnSuccessListener(userCloudDBZoneSnapshot -> processResults(userCloudDBZoneSnapshot, context))
.addOnFailureListener(e -> toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
Note that for the query variable we can create the type of query we need. We will look at examples of this next week (other than just the provided CloudDBZoneQuery.where(User.class)
The final method in this manager is the processResult, here we use a cursor to move over the result returned from the query. For each object in the result we update the max UserID and then add that User to a list. This is the point where we would then do something with that list, perhaps update the UI to show the result or do some other processing.
Java:
private void processResults(CloudDBZoneSnapshot<User> userCloudDBZoneSnapshot, Context context) {
CloudDBZoneObjectList<User> userCursor = userCloudDBZoneSnapshot.getSnapshotObjects();
List<User> userList = new ArrayList<>();
try {
while (userCursor.hasNext()) {
User user = userCursor.next();
updateMaxUserID(user);
userList.add(user);
}
//HAVE USER LIST
} catch (AGConnectCloudDBException e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
} finally {
userCloudDBZoneSnapshot.release();
}
}
We now have all the basic methods we might need to get/set/delete the User object.
We have just a little more setup to do and then we are ready to start using the CloudDB!
As I mentioned earlier we need to init the CloudDB before we can use it anywhere in the app. The best way to do this will be to make it part of the Application class's onCreate method. For example:
Java:
package site.zpweb.barker;
import android.app.Application;
import site.zpweb.barker.db.CloudDBManager;
public class BarkerApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
CloudDBManager.initCloudDB(this);
}
}
Setting this as the application class in your manifest:
XML:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="site.zpweb.barker">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Barker"
android:name=".BarkerApplication">
<activity android:name=".RegisterActivity"></activity>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
And that's it! We are now good to go, next week we will look at how we might actually make use of this functionality as well as expand on ObjectType's and storing more complex data in the cloud!
Can we store custom object cloud db?
Part 3 - More CloudDB​
Last time we looked at the basic setup of the CloudDB service, how to get things configured and how you can get started with the service. Now this is in place lets take a look at the service in more detail. We started working on a CloudDBManager that was going to handle all the communication between the CloudDB service and the rest of the app. This was a great start but before we dig any deep lets look at a few improvements to this.
Listen for Database Changes​
In the current CloudDBManager if we want to check for any changes to the database we had to manually call getAllUsers(). This is fine if we aren't really interested in when changes are made to the database, however if we do want to keep an up to date local copy of the data (for example posts in a feed) we need to look at adding a Snapshot Listener. This will tell the CloudDB service to execute a given query and process the results in real time.
Lets start by defining a new OnSnapShotListener for the Class User when the listener is given a snapshot we pass this into the processResults method we created last week.
Java:
private final OnSnapshotListener<User> snapshotListener = (cloudDBZoneSnapshot, e) -> processResults(cloudDBZoneSnapshot);
Next we create a method that will subscribe that Snapshot Listener to the CloudDBZone with an applied query, in this instance just the simple query to return all Users
Java:
public void addSubscription() {
CloudDBZoneQuery < User > snapshotQuery = CloudDBZoneQuery.where(User.class).equalTo("uid", "");
try {
ListenerHandler handler = cloudDBZone.subscribeSnapshot(snapshotQuery,
CloudDBZoneQuery.CloudDBZoneQueryPolicy.POLICY_QUERY_FROM_CLOUD_ONLY,
snapshotListener);
} catch (Exception e) {
toaster.sendErrorToast(context, e.getLocalizedMessage());
}
}
With this in place we can now tweak the openCloudDBZone method, by using a task based approach we are able to add an onSuccessListener. So long as the zone is successfully opened we can called the addSubscription() method and start listening for new data.
Java:
public void openCloudDBZoneV2() {
CloudDBZoneConfig config = new CloudDBZoneConfig("Barker",
CloudDBZoneConfig.CloudDBZoneSyncProperty.CLOUDDBZONE_CLOUD_CACHE,
CloudDBZoneConfig.CloudDBZoneAccessProperty.CLOUDDBZONE_PUBLIC);
config.setPersistenceEnabled(true);
Task < CloudDBZone > task = cloudDB.openCloudDBZone2(config, true);
task.addOnSuccessListener(zone - > {
cloudDBZone = zone;
addSubscription();
}).addOnFailureListener(e - > toaster.sendErrorToast(context, e.getLocalizedMessage()));
}
Use userList​In the processResults method from last week we took the data snapshot and converted it into a list of User objects. We didn't then do anything with this nor did we have a method to pass that data onto somewhere else in the app. Lets change that!
We will start by creating a new public interface called UserCallBack this will be implemented by any class that wants to use an instance of the CloudDBManager. Currently the interface is pretty simple with just three methods:
Java:
public interface UserCallBack {
void onAddOrQuery(List < User > userList);
void onDelete(List < User > userList);
void onError(String errorMessage);
}
We create a variable for this Call back within the CloudDBManager like:
Java:
private final UserCallBack callBack;
And finally as part of the CloudDBManager init method we require an instance of the UserCallBack
Java:
public CloudDBManager(Context context, UserCallBack callBack) {
cloudDB = AGConnectCloudDB.getInstance();
this.context = context;
this.callBack = callBack;
}
Now when we have processed the result of a query we can pass that data back to the calling class using the callback. For example the processResult method now looks like:
Java:
private void processResults(CloudDBZoneSnapshot < User > userCloudDBZoneSnapshot) {
CloudDBZoneObjectList < User > userCursor = userCloudDBZoneSnapshot.getSnapshotObjects();
List < User > userList = new ArrayList < > ();
try {
while (userCursor.hasNext()) {
User user = userCursor.next();
updateMaxUserID(user);
userList.add(user);
}
callBack.onAddOrQuery(userList);
} catch (AGConnectCloudDBException e) {
callBack.onError(e.getLocalizedMessage());
toaster.sendErrorToast(context, e.getLocalizedMessage());
} finally {
userCloudDBZoneSnapshot.release();
}
}
We now have a way to both pass back the user list and also any error message that might be given during the processing!
The final CloudDBManager should look something like:
A manager class for the AGC CloudDB service
A manager class for the AGC CloudDB service. GitHub Gist: instantly share code, notes, and snippets.
gist.github.com
Authentication Manager​With the changes made above lets take a look at how we can use this to gain access to the database data. Specifically within the `AuthenticationManager`.
First we must now implement the new interface like so:
Java:
public class AuthenticationManager implements CloudDBManager.UserCallBack{
...
@Override
public void onAddOrQuery(List<User> userList) {
}
@Override
public void onDelete(List<User> userList) {
}
@Override
public void onError(String errorMessage) {
}
...
}
The onAddOrQuery method will be given the userList returned when a snapshot is processed, we can implement code here to update the UI or confirm if a user is already registered for example.
In this Classes init method we will also need to pass itself in as the UserCallBack. The setup of the CloudDBManager object will now look like:
Java:
dbManager = new CloudDBManager(context, this);
dbManager.createObjectType();
dbManager.openCloudDBZoneV2();
We are now in a good position to query data, view it and upsert it! Join us next week when we start configuring the other Objects we are going to be using, expand the User object and make the CloudDBManager generic for any CloudDB Object!
devwithzachary said:
Part 0 - Why?
Part 1 - Auth
Part 2 - CloudDB
Over the next couple of months I will be releasing a complete end to end guide to creating an Android app using serverless functionality to completely remove the need for any backend server/hosting etc.
These guides will be made up of a weekly live stream which will then be edited into a YouTube video along with each having a blog post for those that prefer a written guide!
But before we get into the actual guide (which will start with 'Part 1' next week) lets start from the... well start! Why might someone want to build a serverless app? what IS a serverless app? and what does Huawei's AppGallery Connect have to do with it?
Well I'm glad you asked!
What is a serverless app?​If Cloud computing takes away the need to manage the hardware, serverless computing takes away the need to manage the software. Its an extension of cloud computing where the provider handles everything about the servers and simply provides some kind of interface for the user to access their services. This might be in the form of an API, SDK, GUI or all of the above!
Why would I want to build a serverless app?​Server management is in its own right a full time job, from setting up the environment to installing and managing the software stack. Security updates, security hardening, authentication (to name a few) are all things that need to be managed in a traditional backend server setup. By using a serverless service all of this management work is removed, you as the app developer simply have access to the resources you need when you need them.
A couple of examples of why or when you might use a serverless system:
Example one - Prototyping​If you need to build an application prototype quickly, you want something that is just going to work and don't want the hassle of setting everything up! By using a serverless system you have instant access to the services you need, this lets you focus on prototyping the app itself.
devwithzachary said:
Part 0 - Why?
Part 1 - Auth
Part 2 - CloudDB
Over the next couple of months I will be releasing a complete end to end guide to creating an Android app using serverless functionality to completely remove the need for any backend server/hosting etc.
These guides will be made up of a weekly live stream which will then be edited into a YouTube video along with each having a blog post for those that prefer a written guide!
But before we get into the actual guide (which will start with 'Part 1' next week) lets start from the... well start! Why might someone want to build a serverless app? what IS a serverless app? and what does Huawei's AppGallery Connect have to do with it?
Well I'm glad you asked!
What is a serverless app?​If Cloud computing takes away the need to manage the hardware, serverless computing takes away the need to manage the software. Its an extension of cloud computing where the provider handles everything about the servers and simply provides some kind of interface for the user to access their services. This might be in the form of an API, SDK, GUI or all of the above!
Why would I want to build a serverless app?​Server management is in its own right a full time job, from setting up the environment to installing and managing the software stack. Security updates, security hardening, authentication (to name a few) are all things that need to be managed in a traditional backend server setup. By using a serverless service all of this management work is removed, you as the app developer simply have access to the resources you need when you need them.
A couple of examples of why or when you might use a serverless system:
Example one - Prototyping​If you need to build an application prototype quickly, you want something that is just going to work and don't want the hassle of setting everything up! By using a serverless system you have instant access to the services you need, this lets you focus on prototyping the app itself.
Example two - Basic requirements​Many apps have very basic backend server requirements. Perhaps they just want to store users details and setting preferences. Or maybe they just need a way to host and download files. These kinds of requirements tend to mean that a full managed backend server is overkill. When your requirements are simple no one wants to spend hours setting it up and managing a server!
Example Three - Small Team/Limited knowledge​If your a small team (or solo) you might simply not have the knowledge or man power to manage servers. The time taken to learn and maintain that knowledge might significantly impact the amount of time you have to develop your application. Sometimes its just much more cost effective to let another company manage this.
What does Huawei's AppGallery Connect have to do with it?​As part of Huawei's AppGallery platform they now offer a wide range of serverless features and functionality. These services come under the AppGallery Connect suite, including but not limited to, database, web hosting, authentication and storage. These services include generous free tiers which make prototyping and developing using these services even more attractive and cost effective.
Because of this we will be using this platform throughout the development guides as we explore what can be done with a serverless Android app!
Click to expand...
Click to collapse
Example two - Basic requirements​Many apps have very basic backend server requirements. Perhaps they just want to store users details and setting preferences. Or maybe they just need a way to host and download files. These kinds of requirements tend to mean that a full managed backend server is overkill. When your requirements are simple no one wants to spend hours setting it up and managing a server!
Example Three - Small Team/Limited knowledge​If your a small team (or solo) you might simply not have the knowledge or man power to manage servers. The time taken to learn and maintain that knowledge might significantly impact the amount of time you have to develop your application. Sometimes its just much more cost effective to let another company manage this.
What does Huawei's AppGallery Connect have to do with it?​As part of Huawei's AppGallery platform they now offer a wide range of serverless features and functionality. These services come under the AppGallery Connect suite, including but not limited to, database, web hosting, authentication and storage. These services include generous free tiers which make prototyping and developing using these services even more attractive and cost effective.
Because of this we will be using this platform throughout the development guides as we explore what can be done with a serverless Android app!
Click to expand...
Click to collapse
Useful sharing, thanks
Part 4 - Login and Register​Today we are going to look at getting the Login and Register process fully complete. This will include some refactoring to the code we have worked on before.
Authentication Manager​
With a CloudDBManager now in place that is able to handle the User object we created its time we make changes to the AuthenticationManager so that this CloudDBManager is correctly used to retrieve user data at login/register.
Firstly we have a number of variables that we might be passing into the AuthenticationManager. Up until this point we where only passing in a phone number or an email address and this was handled by the contactString variable. However now that we will be accepting registration information, more data needs to be accept.
When logging in the use may be using their mobile phone number or their email address. When registering they might provide either the phone number or email address or both, and in addition a username and display name.
With these elements in mind lets create a simple data object to store this and pass it into the AuthenticationManager as needed. This will look like below, with standard Getters/Setters and constructor.
Java:
public class LoginRegisterData {
String phoneNumber,email,username,displayName;
public LoginRegisterData(String phoneNumber, String email, String username, String displayName) {
this.phoneNumber = phoneNumber;
this.email = email;
this.username = username;
this.displayName = displayName;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getDisplayName() {
return displayName;
}
public void setDisplayName(String displayName) {
this.displayName = displayName;
}
}
We will pass this data into the AuthenticationManager constructor like so:
Java:
public class AuthenticationManager implements CloudDBManager.UserCallBack{
Toaster toaster = new Toaster();
Context context;
int authType;
LoginRegisterData loginRegisterData;
boolean isLogin;
private final CloudDBManager dbManager;
private String loginUserUID = "0";
public AuthenticationManager(Context context, int authType, LoginRegisterData loginRegisterData, boolean isLogin){
this.context = context;
this.authType = authType;
this.loginRegisterData = loginRegisterData;
this.isLogin = isLogin;
dbManager = new CloudDBManager(context, this);
dbManager.createObjectType();
dbManager.openCloudDBZoneV2();
}
...
}
You will also notice that we have removed the contactString from the construct. As this variable has been removed we should also make sure to remove its usage and replace with the correct data from the LoginRegisterData object.
In places where we where expecting this string to contain the email address we should now use loginRegisterData.getEmail() and in places where we where expecting the phone number we should use loginRegisterData.getPhoneNumber().
Next lets take a look at the getUser() method. Up until now we have simply gotten the AGConnectUser for the currently authenticated user, however we haven't actually then done anything with that. Now we should use that authenticated user to get the stored User object from the database.
Java:
private void getUser(){
AGConnectUser user = AGConnectAuth.getInstance().getCurrentUser();
loginUserUID = user.getUid();
CloudDBZoneQuery<User> snapshotQuery = CloudDBZoneQuery.where(User.class).equalTo("uid", loginUserUID);
dbManager.queryUsers(snapshotQuery);
}
...
@Override
public void onQuery(List<User> userList) {
if (userList.size() == 1) {
User user = userList.get(0);
if (user.getUid().equals(loginUserUID)){
saveLoginDetail(user);
proceedToFeed();
}
}
}
Here we are getting the UID of the authenticated user and then querying the database for the user with that UID.
In the onQuery callback we can check that only one user was returned, and then triple check that the returned user does match the UID. From here we call two new methods saveLoginDetail() and proceedToFeed().
saveLoginDetail( ) is used to save a local copy of the logged in users ID and set a flag to say that we are now logged in. This way the next time the user opens the application we can check this flag and the user will not have to login every time they open the app.
Java:
private void saveLoginDetail(User user) {
SharedPreferences preferences = context.getSharedPreferences("loginDetail", 0);
SharedPreferences.Editor editor = preferences.edit();
editor.putBoolean("isLoginedIn", true);
editor.putInt("userId", user.getId());
}
The proceedToFeed() method will simply start the FeedActivity now that we are logged in.
Java:
private void proceedToFeed(){
context.startActivity(new Intent(context, FeedActivity.class));
}
From the Registration side of the process the only thing to change is the addition of being able to set the username and display name as below.
Java:
private void saveRegisteredUser(SignInResult signInResult){
User user = new User();
user.setId(dbManager.getMaxUserID() + 1);
user.setUid(signInResult.getUser().getUid());
user.setUsername(loginRegisterData.getUsername());
user.setDisplayname(loginRegisterData.getDisplayName());
dbManager.upsertUser(user);
}
In the onUpsert call back we use the same two methods saveLoginDetail() and proceedtoFeed() as the login process.
Java:
@Override
public void onUpsert(User user){
saveLoginDetail(user);
proceedToFeed();
}
And that's it! Your AuthenticationManager should now look like this: https://gist.github.com/devwithzachary/97e23d7c813fd917a13ab88de34f9751
Of course as we have now changed the constructor there are some changes that need to be made in both the login and register activities.
Login Activity​Within the MainActivity which is the login activity for us, lets start by creating a simple method to generate the LoginRegisterData object
Java:
private LoginRegisterData getLoginRegisterData() {
String emailString = email.getText().toString().trim();
String phoneString = phone.getText().toString().trim();
return new LoginRegisterData(phoneString, emailString, "", "");
}
As you can see we take the email and phone number input and build the object. At this point if we used this method in the phoneLogin and emailLogin onClick listeners we can see there is code duplication. So instead lets extract a method to trigger the login process.
Java:
private void login(int authType) {
authManager = new AuthenticationManager(MainActivity.this,
authType,
getLoginRegisterData(),
true);
authManager.sendVerifyCode();
}
As you can see we generate the AuthenticateManager passing in the LoginRegisterData and the authType. The OnClick Listeners for each button are now just one line calling this method and passing in the AuthType as needed. The MainActivity should now look like this: https://gist.github.com/devwithzachary/b9cb25f1b86b0503a5f4883345201b9d
Register Activity​For the register activity we do the same process, however we will also add two new EditText fields so that we can accept the user input for username and displayname. Otherwise the process is the same. Create the LoginRegisterData and pass that into the AuthenticationManager. This this in mind the Register activity will look something like: https://gist.github.com/devwithzachary/10a3837b0e9abbc32e1b3dcf083f4276
And that's it! we are now in a good state with the login and register flow which will result in a user being authenticated, logged in and use saving the ID of that user along with setting a flag to confirm the user is logged in.

Categories

Resources