Tree-based Progressive Migration:

 

from Native iOS to React-Native

 

Context

  • Pragmatism-as-a-Service: deliver maximum value in minimal time
     
  • Expertise: React Native, React, Angular, Node, Python and Symfony

Context

Me

  • Joined Theodo ~1  year ago
     
  • Expertise: Angular, Node, React Native, Python.
     
  • No Objective-C experience emoji unicode: 1f628

Context

I WANT TO REDESIGN SOME SCREENS IN MY APP,

CAN WE DO THAT IN REACT-NATIVE ?

CLIENT:

The Challenge

THE CHALLENGE: In Pub page

1 - FINDING THE ENTRY POINT

MPPubViewController *pubViewController = [[MPPubViewController alloc] 
                                                initWithInPub:dictionary
                                                andWithBar:bar
                                                withRewardId:nil];		

[self.navigationController pushViewController:pubViewController animated:YES];

2 - REPLACE WITH REACT-native view

  • Add to your Podfile
pod 'React', :path => '../node_modules/react-native', :subspecs => [
    'Core',
    'RCTText',
    'RCTNetwork',
    'RCTWebSocket', # needed for debugging
]
  • Register a React-Native Component
import { AppRegistry } from 'react-native';
import InPubPage from './src/page/InPubPage';

AppRegistry.registerComponent('InPubPage', () => InPubPage);

2 - REPLACE WITH REACT-native view

Create a controller that contains the magic class: RCTRootView

 #ifdef DEBUG
 NSURL *jsCodeLocation = 
    [NSURL URLWithString:@"http://localhost:8081/index.ios.bundle?platform=ios"];
 #else
 NSURL *jsCodeLocation = 
    [[NSBundle mainBundle] URLForResource:@"index.ios" withExtension:@"bundle"];
 #endif
  
 self.rootView = 
    [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 
                            moduleName:@"InPubPage"
                            launchOptions:nil ];
  
 self.view = self.rootView;
 return self;

2 - REPLACE WITH REACT-native view

- (void) displayModal
-(void) hideModal
{
    [self.view.layer setMasksToBounds:YES];
    [self.view.layer setBackgroundColor:[UIColor colorWithWhite:1 alpha:0].CGColor];

    backgroundView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 
        [[UIScreen mainScreen] bounds].size.width, 
        [[UIScreen mainScreen] bounds].size.height)];
    [backgroundView addSubview:self.view];

    CGRect targetFrame = CGRectMake(0, 0, 
        self.targetView.view.frame.size.width,
        self.targetView.view.frame.size.height);
    [self.view setFrame:targetFrame];

    [self.targetView addChildViewController:self];
    [self.targetView.view addSubview:backgroundView];
    [self didMoveToParentViewController:self.targetView];
}
{
    [self willMoveToParentViewController:nil];
    [backgroundView removeFromSuperview];
    [self removeFromParentViewController];
}

2 - REPLACE WITH REACT-native view

@implementation NavigationManager

+ (id) sharedInstance
{
    ...
}

// ...

- (void) goToInPubPage
{
    self.inPubPageModal = [[RNInPubPageController alloc]
        init:[UIApplication sharedApplication].keyWindow.rootViewController];
    [self.inPubPageModal displayModal];
}

- (void) exitInPubPage
{
    [self.inPubPageModal hideModal];
}

2 - REPLACE WITH REACT-native view

  • Use the Navigation Singleton to create and display the modal

2 - REPLACE WITH REACT-native view

// MPPubViewController *pubViewController = [[MPPubViewController alloc] 
//                                                initWithInPub:dictionary
//                                                andWithBar:bar
//                                                withRewardId:nil];		

// [self.navigationController pushViewController:pubViewController animated:YES];


// Enter React-Native sub-app
[[MPNavigationManager sharedInstance] goToInPubPage];

3 - PASS INFORMATIONS FROM NATIVE TO rEACT-NATIVE

 self.rootView = 
    [[RCTRootView alloc] initWithBundleURL:jsCodeLocation 
                            moduleName:@"InPubPage"
                            initialProperties:@{
                                  @"bar" : self.bar
                            }
                            launchOptions:nil ];

This will be received as props in your component

4 - Back to previous screen

Native

#import "NativeNavigation.h"
#import "NavigationManager.h"

@implementation NativeNatigation

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(exitInPubPage) {
  dispatch_async(dispatch_get_main_queue(), ^{
      [[MPNavigationManager sharedInstance] exitInPubPage];
  });
}
import { NativeModules } from 'react-native';

export const NativeNavigation = NativeModules.NativeNavigation;

React-Native

import { NativeNavigation } from '../nativeNavigation';

// ...

<TouchableOpacity onPress={NativeNavigation.exitInPubPage}>
    Back
</TouchableOpacity>

4 - Back to previous screen

5 - Go To any native screen

5 - Go To any native screen

KO :(

SOLUTION: Tree-based Approach 

  • Identify most connected leaf
  • Find all entry points and replace with React-Native
  • Repeat for all entry points

Conclusion

Useful Links:

PROS

  • No need to know Objective-C
     
  • Faster for designed views
     
  • React-Native

CONS

  • Need to start with leaves
     
  • Making sure states are in sync can be tricky

QUESTIONS ?

Tree-based Progressive Migration - Final

By Jeremy Gotteland

Tree-based Progressive Migration - Final

  • 328