question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

useSafeAreaInsets emits too many results during device rotation

See original GitHub issue

Because of the device performance issue, the repeated emission of the same insets or useless emission from useSafeAreaInsets should be skipped.

When I rotated my device with iPhone X, the console printed out like the following.

AS-IS

[Tue Jan 26 2021 11:40:03.498]  LOG      openFullScreen
[Tue Jan 26 2021 11:40:03.512]  LOG      {"bottom": 34, "left": 0, "right": 0, "top": 44}
[Tue Jan 26 2021 11:40:04.220]  LOG      {"bottom": 21, "left": 44, "right": 0, "top": 0}
[Tue Jan 26 2021 11:40:04.267]  LOG      {"bottom": 21, "left": 44, "right": 0, "top": 0}
[Tue Jan 26 2021 11:40:04.644]  LOG      {"bottom": 21, "left": 44, "right": 0, "top": 0}
[Tue Jan 26 2021 11:40:04.930]  LOG      {"bottom": 0, "left": 0, "right": 0, "top": 0}
[Tue Jan 26 2021 11:40:05.251]  LOG      {"bottom": 0, "left": 44, "right": 0, "top": 0}
[Tue Jan 26 2021 11:40:05.558]  LOG      {"bottom": 21, "left": 44, "right": 44, "top": 0}
[Tue Jan 26 2021 11:40:05.827]  LOG      {"bottom": 21, "left": 44, "right": 44, "top": 0}
[Tue Jan 26 2021 11:40:08.221]  LOG      closeFullScreen
[Tue Jan 26 2021 11:40:08.325]  LOG      {"bottom": 21, "left": 44, "right": 44, "top": 0}
[Tue Jan 26 2021 11:40:08.491]  LOG      {"bottom": 0, "left": 0, "right": 0, "top": 44}
[Tue Jan 26 2021 11:40:08.793]  LOG      {"bottom": 0, "left": 0, "right": 0, "top": 44}
[Tue Jan 26 2021 11:40:09.234]  LOG      {"bottom": 0, "left": 0, "right": 0, "top": 44}
[Tue Jan 26 2021 11:40:09.394]  LOG      {"bottom": 0, "left": 0, "right": 0, "top": 0}
[Tue Jan 26 2021 11:40:09.677]  LOG      {"bottom": 0, "left": 0, "right": 0, "top": 44}
[Tue Jan 26 2021 11:40:09.956]  LOG      {"bottom": 34, "left": 0, "right": 0, "top": 44}
[Tue Jan 26 2021 11:40:10.364]  LOG      {"bottom": 34, "left": 0, "right": 0, "top": 44}

The 6 emissions of the processing are useless and cause performance issues because every layout of components is re-calculated with each inset values.

TO-BE

[Tue Jan 26 2021 11:40:03.498]  LOG      openFullScreen
[Tue Jan 26 2021 11:40:03.512]  LOG      {"bottom": 34, "left": 0, "right": 0, "top": 44}
[Tue Jan 26 2021 11:40:05.827]  LOG      {"bottom": 21, "left": 44, "right": 44, "top": 0}
[Tue Jan 26 2021 11:40:08.221]  LOG      closeFullScreen
[Tue Jan 26 2021 11:40:08.325]  LOG      {"bottom": 21, "left": 44, "right": 44, "top": 0}
[Tue Jan 26 2021 11:40:10.364]  LOG      {"bottom": 34, "left": 0, "right": 0, "top": 44}

Issue Analytics

  • State:open
  • Created 3 years ago
  • Reactions:6
  • Comments:6

github_iconTop GitHub Comments

5reactions
denottercommented, Apr 1, 2021

Having the same issue here. Did anyone find a solution yet?

0reactions
mym0404commented, Oct 4, 2021

In my case, I just made native module for me. There are several files and verbose codes but If you need you can use it. These codes are written with Swift Native Module. If you need to learn how to use Swift for Native Module in RN, look my sample project, documentation.

This module reduces some ignorable event emission but not 100%

SafeArea.swift

@objc(SafeAreaModule)
class SafeAreaModule: RCTEventEmitter{
  static var shared: SafeAreaModule? = nil
  static func viewSafeAreaInsetsDidChange(){
    guard let shared = SafeAreaModule.shared else { return }
    guard shared.getWindow() != nil else { return }
    shared.sendEvent(shared.getSafeAreaInsets())
  }
  
  // MARK: - RCTEventEmitter
  override func supportedEvents() -> [String]! {
    return ["viewSafeAreaInsetsDidChange"]
  }
  
  private func sendEvent(_ insets: Dictionary<String, Int>){
    guard bridge != nil else { return }
    self.sendEvent(withName: "viewSafeAreaInsetsDidChange", body: insets)
  }
  
  @objc
  override func constantsToExport() -> [AnyHashable: Any]!{
    return [:]
  }
  
  @objc
  override static func requiresMainQueueSetup() -> Bool {
    return true
  }
  
  override init() {
    super.init()
    SafeAreaModule.shared = self
  }
  
  @objc func getSafeAreaInsets(_ resolve: @escaping RCTPromiseResolveBlock, reject: @escaping RCTPromiseRejectBlock){
    DispatchQueue.main.async { [weak self] in
      guard let self = self else {
        reject("self not found", "self not found", nil)
        return
      }
      resolve(self.getSafeAreaInsets())
    }
  }
  
  private func getSafeAreaInsets() -> Dictionary<String, Int>{
    var ret = ["top": 0, "bottom": 0, "left": 0, "right": 0]
    let window = getWindow()
    
    if let insets = window?.safeAreaInsets{
      ret["top"] = Int(insets.top)
      ret["bottom"] = Int(insets.bottom)
      ret["left"] = Int(insets.left)
      ret["right"] = Int(insets.right)
    }
    
    return ret
  }
  
  private func getWindow() -> UIWindow?{
    if #available(iOS 13.0, *) {
      return UIApplication.shared.windows[0]
    }else{
      return UIApplication.shared.keyWindow
    }
  }
}

MainViewController.swift

import UIKit

class MainViewController : UIViewController{}

// MARK: -Lifecycle
extension MainViewController{
  override func viewDidLoad() {
    super.viewDidLoad()
  }
  
  override func viewSafeAreaInsetsDidChange() {
    SafeAreaModule.viewSafeAreaInsetsDidChange()
  }
  override func viewDidLayoutSubviews() {
  }
}


Appdelegate.m

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
#if DEBUG && TARGET_OS_SIMULATOR
  InitializeFlipper(application);
#endif
  ...
  
  RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
  
  NSDictionary *appProperties = [RNFBMessagingModule addCustomPropsToUserProps:nil withLaunchOptions:launchOptions];
  
  RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
                                                   moduleName:@"MathKing"
                                            initialProperties:appProperties];
  
  rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
  
  self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
  UIViewController *rootViewController = [MainViewController new];
  rootViewController.view = rootView;
  self.window.rootViewController = rootViewController;
  [self.window makeKeyAndVisible];
  
  return YES;
}

Modules.m

@interface RCT_EXTERN_MODULE(SafeAreaModule, RCTEventEmitter)
RCT_EXTERN_METHOD(
                  getSafeAreaInsets: (RCTPromiseResolveBlock)resolve
                  reject: (RCTPromiseRejectBlock)reject
                  )
@end

SafeAreaModuleIOS.ts

import { NativeEventEmitter, NativeModules } from 'react-native';

import { EdgeInsets } from 'react-native-safe-area-context';

const COMPONENT_NAME = 'SafeAreaModule';

const NativeSafeAreaModule = NativeModules[COMPONENT_NAME];
const EventEmitter = new NativeEventEmitter(NativeSafeAreaModule);

type Listener = (insets: EdgeInsets) => void;
class SafeAreaModuleIOS {
  private subscription;
  private listeners: Listener[] = [];

  constructor() {
    this.subscription = EventEmitter.addListener('viewSafeAreaInsetsDidChange', (event: EdgeInsets) => {
      this.listeners.forEach((listener: Listener) => {
        listener(event);
      });
    });
  }

  addListener(listener: Listener) {
    this.listeners.push(listener);
  }

  removeListener(listener: Listener) {
    const idx = this.listeners.indexOf(listener);
    if (idx === -1) {
      return;
    }
    this.listeners.splice(idx, 1);
  }

  async getSafeAreaInsets(): Promise<EdgeInsets> {
    return NativeSafeAreaModule.getSafeAreaInsets();
  }
}

export default new SafeAreaModuleIOS();

useSafeAreaInsets.ios.ts

import { useCallback, useEffect, useState } from 'react';

import { EdgeInsets } from 'react-native-safe-area-context';
import SafeAreaModuleIOS from './SafeAreaModuleIOS';
import { SentryWrapper } from '../log/SentryWrapper';
import { useMount } from '../../hooks/useMount';

export default function useSafeAreaInsets() {
  const [insets, setInsets] = useState<EdgeInsets>({ top: 0, bottom: 0, left: 0, right: 0 });
  const listener = useCallback((insets: EdgeInsets) => {
    setInsets(insets);
  }, []);

  useMount(() => {
    SafeAreaModuleIOS.getSafeAreaInsets()
      .then(setInsets)
      .catch((e) => {
        SentryWrapper.throw('safe area not found', e);
      });
  });

  useEffect(() => {
    SafeAreaModuleIOS.addListener(listener);
    return () => {
      SafeAreaModuleIOS.removeListener(listener);
    };
  }, [listener]);

  return insets;
}

Read more comments on GitHub >

github_iconTop Results From Across the Web

th3rdwave - Bountysource
Hi, I use your module in a react-native-app, and I presume I need to mock it when I want to use it with...
Read more >
SafeAreaContext - Expo Documentation
This is a more advanced use-case, and might perform worse than SafeAreaView when rotating the device. First, add SafeAreaProvider in your app root...
Read more >
Virtual Pronunciation Coach Developed in React Native - Morioh
Automatically, the list of words to be practice is updated with the results. Challenge. Search: Localize a specific word to pratice:.
Read more >
Ionic 4 Ignored Notch On Iphone X And 11 - ADocLib
Apple's iPhone X has a screen that covers the entire face of the ... behind the notch in landscape view even after Just...
Read more >
react native safe area context Code Example
_handle.onchange (internal/fs/watchers.js:178:28) · jest watch too many open files ... how to lock device orientation using css and javascript ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found