2.8.0 (2024-06-25)
Introducing Unistyles 2.8.0 - the biggest upgrade since version 2.0! This version introduces many new features with backward compatibility and no breaking changes! (still works with React Native 0.73 and Expo SDK 50).
New core
Sometimes it’s hard to move forward and add new features, especially when the code was designed for Unistyles 2.0, which started with two platforms (iOS, Android) and ended up including all possible React Native targets: iOS, Android, tvOS, Windows, macOS, and even visionOS. Additionally, with new architecture in beta, which will stabilize with React Native 0.75 later this year, it’s possible to finally drop some boilerplate and move to pure C++ Turbo Modules.
If you have followed my work for a long time, you know that Unistyles is built on top of a C++ core, which is the heart of the library. That’s why Unistyles 2.8.0 started to transition to a pure C++ TurboModule, and I plan a full conversion later this year. From your perspective, it won’t change anything, but from a core contributor's perspective, maintaining Unistyles will be easier.
The new core is also more friendly for new contributors. Each platform has its own file with atomic functions to get some platform info like getInsets
or getScreenDimensions
. Also, as of now, every platform API is optional , with the use of C++ std::optional
, it means that adding a new exclusive API for Android has no impact on iOS, as iOS won’t implement this method.
The big rewrite is also a big win for package size. I was able to significantly reduce the number of lines and be smart about sharing library logic that is similar across other platforms.
Android Insets
Android insets are difficult to handle. Google has introduced many APIs across different API levels, and there are numerous ways to manage them. In the past, Unistyles implemented a custom algorithm to handle dynamic insets. It was somewhat similar to react-native-safe-area-context
, but I was listening to different events and had to apply ugly debouncing for insets reported by the system. This approach introduced several issues created by developers. That’s why I had to switch to the one and only viable solution, which is the WindowInsetsCompat
API!
Here is a great introduction by Alex Vanyo, a Developer Relations Engineer at Google—I strongly encourage you to watch it (it’s 6 minutes long). Alex said during the video:
(…) querying for an internal system resource to get the status bar height or navigation bar height will lead to awkward extra spacing in the best case or make components impossible to interact with in the worst case.
I strongly agree with him, which is why Unistyles shifts the responsibility for insets to Android itself. Of course, it’s not that easy to query a magical API and get everything right. That’s why Unistyles uses many other techniques to get them correct, for example:
- Handling insets in headless mode (when the app opens from a Push Notification)
- Handling React Native StatusBar translucent/hidden events, as they do not use window flags
- Managing other cases when developers want to hide the navigation bar or use old 3-button navigation
Also, insets are now reported correctly on the first render, so you won’t see weird jumps in the UI when the system reports new insets!
EdgeToEdge by default
Using WindowInsetsCompat
requires your layout to be edgeToEdge. In other words, it means that your StatusBar
is always translucent
and the app can draw below the NavigationBar
. A translucent status bar is also the default when you build your app with Expo. To leverage WindowInsetsCompat
, Unistyles enables it by default.
It’s worth mentioning that from Android 15, the edgeToEdge layout will likely be enabled by default. You can read more about this development here: Android 15 Apps Could Be Forced to Display Edge-to-Edge by Google.
Normal layout vs. EdgeToEdge layout:
I’m eager to hear your feedback about the new Insets API!
Emitting Events from C++
It’s also worth mentioning that Unistyles is now capable of emitting events directly from C++, without the Kotlin and Objective-C proxies! I was able to remove several hundred lines of repetitive code for each platform and simply emit events directly from the core.
iPads / Android Tablets / Foldables
Unistyles now correctly handles resizing windows in multitasking mode (when two apps are displayed side by side). This update also includes support for foldables, such as the Samsung Galaxy Fold!
Pixel Density and Font Scale
UnistylesRuntime
now exposes the fontScale
and pixelRatio
values:
UnistylesRuntime.fontScale // e.g., 1.3
UnistylesRuntime.pixelRatio // e.g., 2.0
Additionally, on Android, the pixelRatio
is taken into account to correctly return window
dimensions. You can change your display settings with accessibility option
New StatusBar and NavigationBar APIs
UnistylesRuntime.statusBar
and UnistylesRuntime.navigationBar
can now be hidden or shown directly with Unistyles. Using these methods ensures that insets will be calculated correctly down to SDK 23 (Android 6.0). Using the expo-navigation-bar
API for older Android versions exposes some discrepancies in insets, as shown below:
Expo API - works correctly for recent SDKs, but has some issues on older Androids:
With Unistyles:
NavigationBar and StatusBar Now Accept Alpha Colors
From Unistyles 2.6.0, you can set the color of the StatusBar
and NavigationBar
. Unfortunately, the colors accepted by default on Android are quite limited. That's why Unistyles now calculates opacity with a super easy API, allowing you to display transparent colors:
UnistylesRuntime.statusBar.setColor('#000000', 0.5) // 6 digits hex with opacity 0 to 1
UnistylesRuntime.statusBar.setColor('#50000000') // 8-digits hex
UnistylesRuntime.navigationBar.setColor('red') // some Android reserved words
Immersive mode
UnistylesRuntime
has gained another new API to enable immersive mode. In immersive mode, you can easily hide both the StatusBar
and NavigationBar
to display your app content fully. This can be especially useful for movies, photo galleries, or other content that requires an edgeToEdge
experience:
UnistylesRuntime.setimmersiveMode(true) // or false to go back to regular UI
Root View background color
You can also change your Root View's background color based on your themes without leaving the library! This API is cross-platform and easy to use:
UnistylesRuntime.setRootViewBackgroundColor('#c2c2c2') // API also accepts opacity
Introducing mini runtime
For convenience, Unistyles was injecting UnistylesRuntime
as an optional second parameter in the createStyleSheet
function:
const stylesheet = createStylesheet((theme, rt) => ({
container: {
flex: 1,
paddingTop: rt.insets.top,
paddingBottom: rt.insets.bottom
}
}))
However, rt
was cluttered with a full list of APIs such as setTheme
or addPlugin
, which don’t make sense to be used directly in your stylesheet. From now on, Unistyles
injects a mini runtime containing only the most important values:
I hope this will make it easier for you to select the required values!
Infer variants types
This feature was requested by several developers, and I'm happy to announce that you can now infer your variants type without manually specifying all the props! Sometimes it’s easier to show than to explain, so please check out the example with the before and after results:
Before: (you need to specify all the props yourself):
interface TypographyProps extends React.PropsWithChildren, TextProps {
style?: TextStyle,
weight?: 'heavy' | 'regular' | 'light',
type?: 'heading' | 'subheading' | 'button' | 'title' | 'subtitle' | 'regular' | 'label'
}
export const Typography: React.FunctionComponent<TypographyProps> = props => {
const { styles, theme } = useStyles(stylesheet, {
weight: props.weight || 'light',
type: props.type || 'regular',
alignCenter: props.isCentered
})
// your JSX
}
After: (much, much cleaner):
import { UnistylesVariants } from 'react-native-unistyles'
interface TypographyProps extends React.PropsWithChildren, TextProps, UnistylesVariants<typeof stylesheet> {
style?: TextStyle
}
export const Typography: React.FunctionComponent<TypographyProps> = props => {
const { styles, theme } = useStyles(stylesheet, {
weight: props.weight || 'light',
type: props.type || 'regular',
alignCenter: props.isCentered
})
// your JSX
}
Summary
Thank you for sticking with Unistyles, which is gaining more and more popularity. I can assure you that I’m spending countless hours making the API stable and thinking of new features. If you have any feature requests, please create a new discussion on GitHub or chat with me on Unistyles Discord.
Happy coding!
P.S.
I will update all the changes in the docs later today.
Commits:
- [iOS] Support breakpoints when iPadOS window frame size changes 110082e90284c5699074a45be90d233d202a9b3e by @vanstinator
- [iOS] unify naming, remove orientation change event (cf3a03a)
- [iOS] access bridge and callInvoker from runtime executor (103de1f)
- [Android] align android (8e40306)
- [Android] convert android module to better structure (97ef33a)
- [C++] make cxx core more extensible (b514171)
- [C++] move device events to cxx (c1d510d)
- [C++] use cxx emitter, add optionals, reduce number of constructor arguments (83d246c)
- [Android] add navigation bar hidden method (fd75612)
- [Android] allow alpha colors for status bar and navigation bar (77bfb9f)
- [Android] expose pixel ratio and font scale (356ae28)
- [Android] add imemrsive mode, adjust insets on lower sdk, create demo with insets (f33488c)
- [Android] add set root view background color method (0711d31)
- [Core] export variants helper typescript (4f6dd0b)
- [iOS] implement missing methods (1ed64d8)
- [Android] insets support headless mode (4d5e9ee)
- [Core] introduce mini runtime (fcc32c5)
- [Android] move away JNI logic from main class (78b4ded)
- [Core] fix: minor argument passign from JS to Bridge [403086d]
- [Core] bump package dependencies (7566a84)
- [tvOS] use new core with tvos (61f8971)
- [visionOS] support visionos with new core (c40b147)
- [windows] add win support for new core (50d4d50)
- [windows] finish implementation for win (314fb1b)
- [macOS] add support for macos and RN 0.73 (671fede)