Skip to content

Commit 5ec382d

Browse files
sahrensfacebook-github-bot
authored andcommitted
New useWindowDimensions hook to replace most Dimensions usage
Summary: Automatically provides and subscribes to dimension updates - super easy usage: ``` function MyComponent(props: Props) { const {width, height, scale, fontScale} = useWindowDimensions(); return <Text ... }; ``` Only window for now - it's what people want 99% of the time, so we'll just shovel out a pit of success for them... There are still cases where `Dimensions` is needed outside of React component render functions, like in GraphQL variables, so we need to keep the existing module. Reviewed By: zackargyle Differential Revision: D16525189 fbshipit-source-id: 0a049fb3be8d92888a8a69e3898d337b93422a09
1 parent 62591ac commit 5ec382d

File tree

6 files changed

+126
-239
lines changed

6 files changed

+126
-239
lines changed

Libraries/FBReactNativeSpec/FBReactNativeSpec/FBReactNativeSpec.h

Lines changed: 2 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -687,107 +687,13 @@ namespace facebook {
687687
} // namespace react
688688
} // namespace facebook
689689

690-
namespace JS {
691-
namespace NativeDeviceInfo {
692-
struct DisplayMetricsIOS {
693-
694-
struct Builder {
695-
struct Input {
696-
RCTRequired<double> width;
697-
RCTRequired<double> height;
698-
RCTRequired<double> scale;
699-
RCTRequired<double> fontScale;
700-
};
701-
702-
/** Initialize with a set of values */
703-
Builder(const Input i);
704-
/** Initialize with an existing DisplayMetricsIOS */
705-
Builder(DisplayMetricsIOS i);
706-
/** Builds the object. Generally used only by the infrastructure. */
707-
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
708-
private:
709-
NSDictionary *(^_factory)(void);
710-
};
711-
712-
static DisplayMetricsIOS fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
713-
NSDictionary *unsafeRawValue() const { return _v; }
714-
private:
715-
DisplayMetricsIOS(NSDictionary *const v) : _v(v) {}
716-
NSDictionary *_v;
717-
};
718-
}
719-
}
720-
721-
namespace JS {
722-
namespace NativeDeviceInfo {
723-
struct DisplayMetricsAndroid {
724-
725-
struct Builder {
726-
struct Input {
727-
RCTRequired<double> width;
728-
RCTRequired<double> height;
729-
RCTRequired<double> scale;
730-
RCTRequired<double> fontScale;
731-
RCTRequired<double> densityDpi;
732-
};
733-
734-
/** Initialize with a set of values */
735-
Builder(const Input i);
736-
/** Initialize with an existing DisplayMetricsAndroid */
737-
Builder(DisplayMetricsAndroid i);
738-
/** Builds the object. Generally used only by the infrastructure. */
739-
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
740-
private:
741-
NSDictionary *(^_factory)(void);
742-
};
743-
744-
static DisplayMetricsAndroid fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
745-
NSDictionary *unsafeRawValue() const { return _v; }
746-
private:
747-
DisplayMetricsAndroid(NSDictionary *const v) : _v(v) {}
748-
NSDictionary *_v;
749-
};
750-
}
751-
}
752-
753-
namespace JS {
754-
namespace NativeDeviceInfo {
755-
struct ConstantsDimensions {
756-
757-
struct Builder {
758-
struct Input {
759-
folly::Optional<JS::NativeDeviceInfo::DisplayMetricsIOS::Builder> window;
760-
folly::Optional<JS::NativeDeviceInfo::DisplayMetricsIOS::Builder> screen;
761-
folly::Optional<JS::NativeDeviceInfo::DisplayMetricsAndroid::Builder> windowPhysicalPixels;
762-
folly::Optional<JS::NativeDeviceInfo::DisplayMetricsAndroid::Builder> screenPhysicalPixels;
763-
};
764-
765-
/** Initialize with a set of values */
766-
Builder(const Input i);
767-
/** Initialize with an existing ConstantsDimensions */
768-
Builder(ConstantsDimensions i);
769-
/** Builds the object. Generally used only by the infrastructure. */
770-
NSDictionary *buildUnsafeRawValue() const { return _factory(); };
771-
private:
772-
NSDictionary *(^_factory)(void);
773-
};
774-
775-
static ConstantsDimensions fromUnsafeRawValue(NSDictionary *const v) { return {v}; }
776-
NSDictionary *unsafeRawValue() const { return _v; }
777-
private:
778-
ConstantsDimensions(NSDictionary *const v) : _v(v) {}
779-
NSDictionary *_v;
780-
};
781-
}
782-
}
783-
784690
namespace JS {
785691
namespace NativeDeviceInfo {
786692
struct Constants {
787693

788694
struct Builder {
789695
struct Input {
790-
RCTRequired<JS::NativeDeviceInfo::ConstantsDimensions::Builder> Dimensions;
696+
RCTRequired<id<NSObject>> Dimensions;
791697
folly::Optional<bool> isIPhoneX_deprecated;
792698
};
793699

@@ -2520,57 +2426,10 @@ inline JS::NativeBlobModule::Constants::Builder::Builder(const Input i) : _facto
25202426
inline JS::NativeBlobModule::Constants::Builder::Builder(Constants i) : _factory(^{
25212427
return i.unsafeRawValue();
25222428
}) {}
2523-
inline JS::NativeDeviceInfo::DisplayMetricsIOS::Builder::Builder(const Input i) : _factory(^{
2524-
NSMutableDictionary *d = [NSMutableDictionary new];
2525-
auto width = i.width.get();
2526-
d[@"width"] = @(width);
2527-
auto height = i.height.get();
2528-
d[@"height"] = @(height);
2529-
auto scale = i.scale.get();
2530-
d[@"scale"] = @(scale);
2531-
auto fontScale = i.fontScale.get();
2532-
d[@"fontScale"] = @(fontScale);
2533-
return d;
2534-
}) {}
2535-
inline JS::NativeDeviceInfo::DisplayMetricsIOS::Builder::Builder(DisplayMetricsIOS i) : _factory(^{
2536-
return i.unsafeRawValue();
2537-
}) {}
2538-
inline JS::NativeDeviceInfo::DisplayMetricsAndroid::Builder::Builder(const Input i) : _factory(^{
2539-
NSMutableDictionary *d = [NSMutableDictionary new];
2540-
auto width = i.width.get();
2541-
d[@"width"] = @(width);
2542-
auto height = i.height.get();
2543-
d[@"height"] = @(height);
2544-
auto scale = i.scale.get();
2545-
d[@"scale"] = @(scale);
2546-
auto fontScale = i.fontScale.get();
2547-
d[@"fontScale"] = @(fontScale);
2548-
auto densityDpi = i.densityDpi.get();
2549-
d[@"densityDpi"] = @(densityDpi);
2550-
return d;
2551-
}) {}
2552-
inline JS::NativeDeviceInfo::DisplayMetricsAndroid::Builder::Builder(DisplayMetricsAndroid i) : _factory(^{
2553-
return i.unsafeRawValue();
2554-
}) {}
2555-
inline JS::NativeDeviceInfo::ConstantsDimensions::Builder::Builder(const Input i) : _factory(^{
2556-
NSMutableDictionary *d = [NSMutableDictionary new];
2557-
auto window = i.window;
2558-
d[@"window"] = window.hasValue() ? window.value().buildUnsafeRawValue() : nil;
2559-
auto screen = i.screen;
2560-
d[@"screen"] = screen.hasValue() ? screen.value().buildUnsafeRawValue() : nil;
2561-
auto windowPhysicalPixels = i.windowPhysicalPixels;
2562-
d[@"windowPhysicalPixels"] = windowPhysicalPixels.hasValue() ? windowPhysicalPixels.value().buildUnsafeRawValue() : nil;
2563-
auto screenPhysicalPixels = i.screenPhysicalPixels;
2564-
d[@"screenPhysicalPixels"] = screenPhysicalPixels.hasValue() ? screenPhysicalPixels.value().buildUnsafeRawValue() : nil;
2565-
return d;
2566-
}) {}
2567-
inline JS::NativeDeviceInfo::ConstantsDimensions::Builder::Builder(ConstantsDimensions i) : _factory(^{
2568-
return i.unsafeRawValue();
2569-
}) {}
25702429
inline JS::NativeDeviceInfo::Constants::Builder::Builder(const Input i) : _factory(^{
25712430
NSMutableDictionary *d = [NSMutableDictionary new];
25722431
auto Dimensions = i.Dimensions.get();
2573-
d[@"Dimensions"] = Dimensions.buildUnsafeRawValue();
2432+
d[@"Dimensions"] = Dimensions;
25742433
auto isIPhoneX_deprecated = i.isIPhoneX_deprecated;
25752434
d[@"isIPhoneX_deprecated"] = isIPhoneX_deprecated.hasValue() ? @((BOOL)isIPhoneX_deprecated.value()) : nil;
25762435
return d;

Libraries/Utilities/Dimensions.js

Lines changed: 59 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -10,90 +10,84 @@
1010

1111
'use strict';
1212

13-
const EventEmitter = require('../vendor/emitter/EventEmitter');
14-
const Platform = require('./Platform');
15-
const RCTDeviceEventEmitter = require('../EventEmitter/RCTDeviceEventEmitter');
13+
import EventEmitter from '../vendor/emitter/EventEmitter';
14+
import RCTDeviceEventEmitter from '../EventEmitter/RCTDeviceEventEmitter';
15+
import NativeDeviceInfo, {
16+
type DisplayMetrics,
17+
type DimensionsPayload,
18+
} from './NativeDeviceInfo';
19+
import invariant from 'invariant';
1620

17-
import NativeDeviceInfo from './NativeDeviceInfo';
18-
19-
const invariant = require('invariant');
21+
type DimensionsValue = {window?: DisplayMetrics, screen?: DisplayMetrics};
2022

2123
const eventEmitter = new EventEmitter();
2224
let dimensionsInitialized = false;
23-
const dimensions = {};
25+
let dimensions: DimensionsValue;
26+
2427
class Dimensions {
28+
/**
29+
* NOTE: `useWindowDimensions` is the preffered API for React components.
30+
*
31+
* Initial dimensions are set before `runApplication` is called so they should
32+
* be available before any other require's are run, but may be updated later.
33+
*
34+
* Note: Although dimensions are available immediately, they may change (e.g
35+
* due to device rotation) so any rendering logic or styles that depend on
36+
* these constants should try to call this function on every render, rather
37+
* than caching the value (for example, using inline styles rather than
38+
* setting a value in a `StyleSheet`).
39+
*
40+
* Example: `const {height, width} = Dimensions.get('window');`
41+
*
42+
* @param {string} dim Name of dimension as defined when calling `set`.
43+
* @returns {Object?} Value for the dimension.
44+
*/
45+
static get(dim: string): Object {
46+
invariant(dimensions[dim], 'No dimension set for key ' + dim);
47+
return dimensions[dim];
48+
}
49+
2550
/**
2651
* This should only be called from native code by sending the
2752
* didUpdateDimensions event.
2853
*
2954
* @param {object} dims Simple string-keyed object of dimensions to set
3055
*/
31-
static set(dims: {[key: string]: any}): void {
56+
static set(dims: $ReadOnly<{[key: string]: any}>): void {
3257
// We calculate the window dimensions in JS so that we don't encounter loss of
3358
// precision in transferring the dimensions (which could be non-integers) over
3459
// the bridge.
35-
if (dims && dims.windowPhysicalPixels) {
36-
// parse/stringify => Clone hack
37-
dims = JSON.parse(JSON.stringify(dims));
38-
39-
const windowPhysicalPixels = dims.windowPhysicalPixels;
40-
dims.window = {
60+
let {screen, window} = dims;
61+
const {windowPhysicalPixels} = dims;
62+
if (windowPhysicalPixels) {
63+
window = {
4164
width: windowPhysicalPixels.width / windowPhysicalPixels.scale,
4265
height: windowPhysicalPixels.height / windowPhysicalPixels.scale,
4366
scale: windowPhysicalPixels.scale,
4467
fontScale: windowPhysicalPixels.fontScale,
4568
};
46-
if (Platform.OS === 'android') {
47-
// Screen and window dimensions are different on android
48-
const screenPhysicalPixels = dims.screenPhysicalPixels;
49-
dims.screen = {
50-
width: screenPhysicalPixels.width / screenPhysicalPixels.scale,
51-
height: screenPhysicalPixels.height / screenPhysicalPixels.scale,
52-
scale: screenPhysicalPixels.scale,
53-
fontScale: screenPhysicalPixels.fontScale,
54-
};
55-
56-
// delete so no callers rely on this existing
57-
delete dims.screenPhysicalPixels;
58-
} else {
59-
dims.screen = dims.window;
60-
}
61-
// delete so no callers rely on this existing
62-
delete dims.windowPhysicalPixels;
69+
}
70+
const {screenPhysicalPixels} = dims;
71+
if (screenPhysicalPixels) {
72+
screen = {
73+
width: screenPhysicalPixels.width / screenPhysicalPixels.scale,
74+
height: screenPhysicalPixels.height / screenPhysicalPixels.scale,
75+
scale: screenPhysicalPixels.scale,
76+
fontScale: screenPhysicalPixels.fontScale,
77+
};
78+
} else if (screen == null) {
79+
screen = window;
6380
}
6481

65-
Object.assign(dimensions, dims);
82+
dimensions = {window, screen};
6683
if (dimensionsInitialized) {
6784
// Don't fire 'change' the first time the dimensions are set.
68-
eventEmitter.emit('change', {
69-
window: dimensions.window,
70-
screen: dimensions.screen,
71-
});
85+
eventEmitter.emit('change', dimensions);
7286
} else {
7387
dimensionsInitialized = true;
7488
}
7589
}
7690

77-
/**
78-
* Initial dimensions are set before `runApplication` is called so they should
79-
* be available before any other require's are run, but may be updated later.
80-
*
81-
* Note: Although dimensions are available immediately, they may change (e.g
82-
* due to device rotation) so any rendering logic or styles that depend on
83-
* these constants should try to call this function on every render, rather
84-
* than caching the value (for example, using inline styles rather than
85-
* setting a value in a `StyleSheet`).
86-
*
87-
* Example: `var {height, width} = Dimensions.get('window');`
88-
*
89-
* @param {string} dim Name of dimension as defined when calling `set`.
90-
* @returns {Object?} Value for the dimension.
91-
*/
92-
static get(dim: string): Object {
93-
invariant(dimensions[dim], 'No dimension set for key ' + dim);
94-
return dimensions[dim];
95-
}
96-
9791
/**
9892
* Add an event handler. Supported events:
9993
*
@@ -102,7 +96,7 @@ class Dimensions {
10296
* are the same as the return values of `Dimensions.get('window')` and
10397
* `Dimensions.get('screen')`, respectively.
10498
*/
105-
static addEventListener(type: string, handler: Function) {
99+
static addEventListener(type: 'change', handler: Function) {
106100
invariant(
107101
type === 'change',
108102
'Trying to subscribe to unknown event: "%s"',
@@ -114,7 +108,7 @@ class Dimensions {
114108
/**
115109
* Remove an event handler.
116110
*/
117-
static removeEventListener(type: string, handler: Function) {
111+
static removeEventListener(type: 'change', handler: Function) {
118112
invariant(
119113
type === 'change',
120114
'Trying to remove listener for unknown event: "%s"',
@@ -124,25 +118,13 @@ class Dimensions {
124118
}
125119
}
126120

127-
let dims: ?{[key: string]: any} =
128-
global.nativeExtensions &&
129-
global.nativeExtensions.DeviceInfo &&
130-
global.nativeExtensions.DeviceInfo.Dimensions;
131-
let nativeExtensionsEnabled = true;
132-
if (!dims) {
133-
dims = NativeDeviceInfo.getConstants().Dimensions;
134-
nativeExtensionsEnabled = false;
135-
}
136-
137-
invariant(
138-
dims,
139-
'Either DeviceInfo native extension or DeviceInfo Native Module must be registered',
140-
);
141-
Dimensions.set(dims);
142-
if (!nativeExtensionsEnabled) {
143-
RCTDeviceEventEmitter.addListener('didUpdateDimensions', function(update) {
121+
// Subscribe before calling getConstants to make sure we don't miss any updates in between.
122+
RCTDeviceEventEmitter.addListener(
123+
'didUpdateDimensions',
124+
(update: DimensionsPayload) => {
144125
Dimensions.set(update);
145-
});
146-
}
126+
},
127+
);
128+
Dimensions.set(NativeDeviceInfo.getConstants().Dimensions);
147129

148130
module.exports = Dimensions;

0 commit comments

Comments
 (0)