1c3ab6167SBrent Vatne--- 2162a2b31SAman Mittaltitle: 'Tutorial: Creating a native module' 34d2795ecSAman Mittalsidebar_title: Create a native module 44d2795ecSAman Mittaldescription: A tutorial on creating a native module with Expo modules API. 5c3ab6167SBrent Vatne--- 6c3ab6167SBrent Vatne 7c3ab6167SBrent Vatneimport { Terminal } from '~/ui/components/Snippet'; 8*cfa99e0fSAman Mittalimport { BoxLink } from '~/ui/components/BoxLink'; 9*cfa99e0fSAman Mittalimport { BookOpen02Icon, Grid01Icon } from '@expo/styleguide-icons'; 10c3ab6167SBrent Vatne 11162a2b31SAman MittalIn this tutorial, we are going to build a module that stores the user's preferred app theme - either dark, light, or system. We'll use [`UserDefaults`](https://developer.apple.com/documentation/foundation/userdefaults) on iOS and [`SharedPreferences`](https://developer.android.com/reference/android/content/SharedPreferences) on Android. It is possible to implement web support using [`localStorage`](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage), but we'll leave that as an exercise for the reader. 12c3ab6167SBrent Vatne 13c3ab6167SBrent Vatne## 1. Initialize a new module 14c3ab6167SBrent Vatne 15c3ab6167SBrent VatneFirst, we'll create a new module. On this page we will use the name `expo-settings`/`ExpoSettings`. You can name it whatever you like, just adjust the instructions accordingly: 16c3ab6167SBrent Vatne 17c3ab6167SBrent Vatne<Terminal cmd={['$ npx create-expo-module expo-settings']} /> 18c3ab6167SBrent Vatne 19c3ab6167SBrent Vatne> **Tip**: Since you aren't going to actually ship this library, you can hit <kbd>return</kbd> for all of the prompts to accept the default values. 20c3ab6167SBrent Vatne 21c3ab6167SBrent Vatne## 2. Set up our workspace 22c3ab6167SBrent Vatne 23c3ab6167SBrent VatneNow let's clean up the default module a little bit so we have more of a clean slate and delete the view module that we won't use in this guide. 24c3ab6167SBrent Vatne 25c3ab6167SBrent Vatne<Terminal 26b06088cdSBrent Vatne cmdCopy="cd expo-settings && rm ios/ExpoSettingsView.swift && rm android/src/main/java/expo/modules/settings/ExpoSettingsView.kt && rm src/ExpoSettingsView.tsx src/ExpoSettingsView.web.tsx src/ExpoSettingsModule.web.ts src/ExpoSettings.types.ts" 27c3ab6167SBrent Vatne cmd={[ 28c3ab6167SBrent Vatne '$ cd expo-settings', 29c3ab6167SBrent Vatne '$ rm ios/ExpoSettingsView.swift', 30b06088cdSBrent Vatne '$ rm android/src/main/java/expo/modules/settings/ExpoSettingsView.kt', 31c3ab6167SBrent Vatne '$ rm src/ExpoSettingsView.tsx src/ExpoSettings.types.ts', 32c3ab6167SBrent Vatne '$ rm src/ExpoSettingsView.web.tsx src/ExpoSettingsModule.web.ts', 33c3ab6167SBrent Vatne ]} 34c3ab6167SBrent Vatne/> 35c3ab6167SBrent Vatne 36c3ab6167SBrent VatneFind the following files and replace them with the provided minimal boilerplate: 37c3ab6167SBrent Vatne 38c3ab6167SBrent Vatne```swift ios/ExpoSettingsModule.swift 39c3ab6167SBrent Vatneimport ExpoModulesCore 40c3ab6167SBrent Vatne 41c3ab6167SBrent Vatnepublic class ExpoSettingsModule: Module { 42c3ab6167SBrent Vatne public func definition() -> ModuleDefinition { 43c3ab6167SBrent Vatne Name("ExpoSettings") 44c3ab6167SBrent Vatne 45c3ab6167SBrent Vatne Function("getTheme") { () -> String in 46c3ab6167SBrent Vatne "system" 47c3ab6167SBrent Vatne } 48c3ab6167SBrent Vatne } 49c3ab6167SBrent Vatne} 50c3ab6167SBrent Vatne``` 51c3ab6167SBrent Vatne 52b06088cdSBrent Vatne```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt 53c3ab6167SBrent Vatnepackage expo.modules.settings 54c3ab6167SBrent Vatne 55c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.Module 56c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.ModuleDefinition 57c3ab6167SBrent Vatne 58c3ab6167SBrent Vatneclass ExpoSettingsModule : Module() { 59c3ab6167SBrent Vatne override fun definition() = ModuleDefinition { 60c3ab6167SBrent Vatne Name("ExpoSettings") 61c3ab6167SBrent Vatne 62c3ab6167SBrent Vatne Function("getTheme") { 63c3ab6167SBrent Vatne return@Function "system" 64c3ab6167SBrent Vatne } 65c3ab6167SBrent Vatne } 66c3ab6167SBrent Vatne} 67c3ab6167SBrent Vatne``` 68c3ab6167SBrent Vatne 69da53c187SŁukasz Kosmaty```typescript src/index.ts 70c3ab6167SBrent Vatneimport ExpoSettingsModule from './ExpoSettingsModule'; 71c3ab6167SBrent Vatne 72c3ab6167SBrent Vatneexport function getTheme(): string { 73c3ab6167SBrent Vatne return ExpoSettingsModule.getTheme(); 74c3ab6167SBrent Vatne} 75c3ab6167SBrent Vatne``` 76c3ab6167SBrent Vatne 77da53c187SŁukasz Kosmaty```typescript example/App.tsx 78c3ab6167SBrent Vatneimport * as Settings from 'expo-settings'; 79c3ab6167SBrent Vatneimport { Text, View } from 'react-native'; 80c3ab6167SBrent Vatne 81c3ab6167SBrent Vatneexport default function App() { 82c3ab6167SBrent Vatne return ( 83c3ab6167SBrent Vatne <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> 84c3ab6167SBrent Vatne <Text>Theme: {Settings.getTheme()}</Text> 85c3ab6167SBrent Vatne </View> 86c3ab6167SBrent Vatne ); 87c3ab6167SBrent Vatne} 88c3ab6167SBrent Vatne``` 89c3ab6167SBrent Vatne 90c3ab6167SBrent Vatne## 3. Run the example project 91c3ab6167SBrent Vatne 92c3ab6167SBrent VatneNow let's run the example project to make sure everything is working. We'll need to start the TypeScript compiler to watch for changes and rebuild the module JavaScript, and separately in another terminal window we'll compile and run the example app. 93c3ab6167SBrent Vatne 94c3ab6167SBrent Vatne<Terminal 95c3ab6167SBrent Vatne cmdCopy="npm run build" 96c3ab6167SBrent Vatne cmd={[ 97c3ab6167SBrent Vatne '# Run this in the root of the project to start the TypeScript compiler', 98c3ab6167SBrent Vatne '$ npm run build', 99c3ab6167SBrent Vatne ]} 100c3ab6167SBrent Vatne/> 101c3ab6167SBrent Vatne 102c3ab6167SBrent Vatne<Terminal 103c3ab6167SBrent Vatne cmdCopy="cd example && npx expo run:ios" 104c3ab6167SBrent Vatne cmd={[ 105c3ab6167SBrent Vatne '$ cd example', 106c3ab6167SBrent Vatne '# Run the example app on iOS', 107c3ab6167SBrent Vatne '$ npx expo run:ios', 108c3ab6167SBrent Vatne '# Run the example app on Android', 109c3ab6167SBrent Vatne '$ npx expo run:android', 110c3ab6167SBrent Vatne ]} 111c3ab6167SBrent Vatne/> 112c3ab6167SBrent Vatne 113da53c187SŁukasz KosmatyWe should now see the text "Theme: system" in the center of the screen when we launch the example app. The value `"system"` is the result of synchronously calling the `getTheme()` function in the native module. We'll change this value in the next step. 114c3ab6167SBrent Vatne 115c3ab6167SBrent Vatne## 4. Get, set, and persist the theme preference value 116c3ab6167SBrent Vatne 117c3ab6167SBrent Vatne### iOS native module 118c3ab6167SBrent Vatne 119c3ab6167SBrent VatneTo read the value on iOS, we can look for a `UserDefaults` string under the key `"theme"`, and fall back to `"system"` if there isn't any. 120c3ab6167SBrent Vatne 121c3ab6167SBrent VatneTo set the value, we can use `UserDefaults`'s `set(_:forKey:)` method. We'll make our `setTheme` function accept a value of type `String`. 122c3ab6167SBrent Vatne 123c3ab6167SBrent Vatne```swift ios/ExpoSettingsModule.swift 124c3ab6167SBrent Vatneimport ExpoModulesCore 125c3ab6167SBrent Vatne 126c3ab6167SBrent Vatnepublic class ExpoSettingsModule: Module { 127c3ab6167SBrent Vatne public func definition() -> ModuleDefinition { 128c3ab6167SBrent Vatne Name("ExpoSettings") 129c3ab6167SBrent Vatne 130c3ab6167SBrent Vatne Function("setTheme") { (theme: String) -> Void in 131c3ab6167SBrent Vatne UserDefaults.standard.set(theme, forKey:"theme") 132c3ab6167SBrent Vatne } 133c3ab6167SBrent Vatne 134c3ab6167SBrent Vatne Function("getTheme") { () -> String in 135c3ab6167SBrent Vatne UserDefaults.standard.string(forKey: "theme") ?? "system" 136c3ab6167SBrent Vatne } 137c3ab6167SBrent Vatne } 138c3ab6167SBrent Vatne} 139c3ab6167SBrent Vatne``` 140c3ab6167SBrent Vatne 141c3ab6167SBrent Vatne### Android native module 142c3ab6167SBrent Vatne 143c3ab6167SBrent VatneTo read the value, we can look for a `SharedPreferences` string under the key `"theme"`, and fall back to `"system"` if there isn't any. We can get the `SharedPreferences` instance from the `reactContext` (a React Native [ContextWrapper](https://developer.android.com/reference/android/content/ContextWrapper)) using `getSharedPreferences()`. 144c3ab6167SBrent Vatne 145c3ab6167SBrent VatneTo set the value, we can use `SharedPreferences`'s `edit()` method to get an `Editor` instance, and then use `putString()` to set the value. We'll make our `setTheme` function accept a value of type `String`. 146c3ab6167SBrent Vatne 147b06088cdSBrent Vatne```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt 148c3ab6167SBrent Vatnepackage expo.modules.settings 149c3ab6167SBrent Vatne 150c3ab6167SBrent Vatneimport android.content.Context 151c3ab6167SBrent Vatneimport android.content.SharedPreferences 152c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.Module 153c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.ModuleDefinition 154c3ab6167SBrent Vatne 155c3ab6167SBrent Vatneclass ExpoSettingsModule : Module() { 156c3ab6167SBrent Vatne override fun definition() = ModuleDefinition { 157c3ab6167SBrent Vatne Name("ExpoSettings") 158c3ab6167SBrent Vatne 159c3ab6167SBrent Vatne Function("setTheme") { theme: String -> 160c3ab6167SBrent Vatne getPreferences().edit().putString("theme", theme).commit() 161c3ab6167SBrent Vatne } 162c3ab6167SBrent Vatne 163c3ab6167SBrent Vatne Function("getTheme") { 164c3ab6167SBrent Vatne return@Function getPreferences().getString("theme", "system") 165c3ab6167SBrent Vatne } 166c3ab6167SBrent Vatne } 167c3ab6167SBrent Vatne 168c3ab6167SBrent Vatne private val context 169c3ab6167SBrent Vatne get() = requireNotNull(appContext.reactContext) 170c3ab6167SBrent Vatne 171c3ab6167SBrent Vatne private fun getPreferences(): SharedPreferences { 172c3ab6167SBrent Vatne return context.getSharedPreferences(context.packageName + ".settings", Context.MODE_PRIVATE) 173c3ab6167SBrent Vatne } 174c3ab6167SBrent Vatne} 175c3ab6167SBrent Vatne``` 176c3ab6167SBrent Vatne 177c3ab6167SBrent Vatne### TypeScript module 178c3ab6167SBrent Vatne 179c3ab6167SBrent VatneNow we can call our native modules from TypeScript. 180c3ab6167SBrent Vatne 181da53c187SŁukasz Kosmaty```typescript src/index.ts 182c3ab6167SBrent Vatneimport ExpoSettingsModule from './ExpoSettingsModule'; 183c3ab6167SBrent Vatne 184c3ab6167SBrent Vatneexport function getTheme(): string { 185c3ab6167SBrent Vatne return ExpoSettingsModule.getTheme(); 186c3ab6167SBrent Vatne} 187c3ab6167SBrent Vatne 188c3ab6167SBrent Vatneexport function setTheme(theme: string): void { 189c3ab6167SBrent Vatne return ExpoSettingsModule.setTheme(theme); 190c3ab6167SBrent Vatne} 191c3ab6167SBrent Vatne``` 192c3ab6167SBrent Vatne 193c3ab6167SBrent Vatne### Example app 194c3ab6167SBrent Vatne 195c3ab6167SBrent VatneWe can now use the `Settings` API in our example app. 196c3ab6167SBrent Vatne 197da53c187SŁukasz Kosmaty```typescript example/App.tsx 198c3ab6167SBrent Vatneimport * as Settings from 'expo-settings'; 199c3ab6167SBrent Vatneimport { Button, Text, View } from 'react-native'; 200c3ab6167SBrent Vatne 201c3ab6167SBrent Vatneexport default function App() { 202c3ab6167SBrent Vatne const theme = Settings.getTheme(); 203c3ab6167SBrent Vatne // Toggle between dark and light theme 204c3ab6167SBrent Vatne const nextTheme = theme === 'dark' ? 'light' : 'dark'; 205c3ab6167SBrent Vatne 206c3ab6167SBrent Vatne return ( 207c3ab6167SBrent Vatne <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> 208c3ab6167SBrent Vatne <Text>Theme: {Settings.getTheme()}</Text> 209c3ab6167SBrent Vatne <Button title={`Set theme to ${nextTheme}`} onPress={() => Settings.setTheme(nextTheme)} /> 210c3ab6167SBrent Vatne </View> 211c3ab6167SBrent Vatne ); 212c3ab6167SBrent Vatne} 213c3ab6167SBrent Vatne``` 214c3ab6167SBrent Vatne 215c3ab6167SBrent VatneWhen we re-build and run the app, we'll see the "system" them is still set. When we press the button, nothing happens. When you reload the app, you'll see the theme has changed. This is because we're never fetching the new theme value and re-rendering the app. We'll fix this in the next step. 216c3ab6167SBrent Vatne 217c3ab6167SBrent Vatne## 5. Emit change events for the theme value 218c3ab6167SBrent Vatne 219c3ab6167SBrent VatneWe can ensure that developers using our API can react to changes in the theme value by emitting a change event when the value changes. We'll use the [Events](/modules/module-api/#events) definition component to describe events that our module can emit, `sendEvent` to emit the event from native, and the [EventEmitter](/modules/module-api/#sending-events) API to subscribe to events in JavaScript. Our event payload will be `{ theme: string }`. 220c3ab6167SBrent Vatne 221c3ab6167SBrent Vatne### iOS native module 222c3ab6167SBrent Vatne 223c3ab6167SBrent Vatne```swift ios/ExpoSettingsModule.swift 224c3ab6167SBrent Vatneimport ExpoModulesCore 225c3ab6167SBrent Vatne 226c3ab6167SBrent Vatnepublic class ExpoSettingsModule: Module { 227c3ab6167SBrent Vatne public func definition() -> ModuleDefinition { 228c3ab6167SBrent Vatne Name("ExpoSettings") 229c3ab6167SBrent Vatne 230c3ab6167SBrent Vatne Events("onChangeTheme") 231c3ab6167SBrent Vatne 232c3ab6167SBrent Vatne Function("setTheme") { (theme: String) -> Void in 233c3ab6167SBrent Vatne UserDefaults.standard.set(theme, forKey:"theme") 234c3ab6167SBrent Vatne sendEvent("onChangeTheme", [ 235c3ab6167SBrent Vatne "theme": theme 236c3ab6167SBrent Vatne ]) 237c3ab6167SBrent Vatne } 238c3ab6167SBrent Vatne 239c3ab6167SBrent Vatne Function("getTheme") { () -> String in 240c3ab6167SBrent Vatne UserDefaults.standard.string(forKey: "theme") ?? "system" 241c3ab6167SBrent Vatne } 242c3ab6167SBrent Vatne } 243c3ab6167SBrent Vatne} 244c3ab6167SBrent Vatne``` 245c3ab6167SBrent Vatne 246c3ab6167SBrent Vatne### Android native module 247c3ab6167SBrent Vatne 248c3ab6167SBrent VatneEvents payloads are represented as [`Bundle`](https://developer.android.com/reference/android/os/Bundle.html) instances on Android, which we can create using the [`bundleOf`](<https://developer.android.com/reference/kotlin/androidx/core/os/package-summary#bundleOf(kotlin.Array)>) function. 249c3ab6167SBrent Vatne 250b06088cdSBrent Vatne```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt 251c3ab6167SBrent Vatnepackage expo.modules.settings 252c3ab6167SBrent Vatne 253c3ab6167SBrent Vatneimport android.content.Context 254c3ab6167SBrent Vatneimport android.content.SharedPreferences 255c3ab6167SBrent Vatneimport androidx.core.os.bundleOf 256c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.Module 257c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.ModuleDefinition 258c3ab6167SBrent Vatne 259c3ab6167SBrent Vatneclass ExpoSettingsModule : Module() { 260c3ab6167SBrent Vatne override fun definition() = ModuleDefinition { 261c3ab6167SBrent Vatne Name("ExpoSettings") 262c3ab6167SBrent Vatne 263c3ab6167SBrent Vatne Events("onChangeTheme") 264c3ab6167SBrent Vatne 265c3ab6167SBrent Vatne Function("setTheme") { theme: String -> 266c3ab6167SBrent Vatne getPreferences().edit().putString("theme", theme).commit() 267c3ab6167SBrent Vatne [email protected]("onChangeTheme", bundleOf("theme" to theme)) 268c3ab6167SBrent Vatne } 269c3ab6167SBrent Vatne 270c3ab6167SBrent Vatne Function("getTheme") { 271c3ab6167SBrent Vatne return@Function getPreferences().getString("theme", "system") 272c3ab6167SBrent Vatne } 273c3ab6167SBrent Vatne } 274c3ab6167SBrent Vatne 275c3ab6167SBrent Vatne private val context 276c3ab6167SBrent Vatne get() = requireNotNull(appContext.reactContext) 277c3ab6167SBrent Vatne 278c3ab6167SBrent Vatne private fun getPreferences(): SharedPreferences { 279c3ab6167SBrent Vatne return context.getSharedPreferences(context.packageName + ".settings", Context.MODE_PRIVATE) 280c3ab6167SBrent Vatne } 281c3ab6167SBrent Vatne} 282c3ab6167SBrent Vatne``` 283c3ab6167SBrent Vatne 284c3ab6167SBrent Vatne### TypeScript module 285c3ab6167SBrent Vatne 286da53c187SŁukasz Kosmaty```typescript src/index.ts 287c3ab6167SBrent Vatneimport { EventEmitter, Subscription } from 'expo-modules-core'; 288c3ab6167SBrent Vatneimport ExpoSettingsModule from './ExpoSettingsModule'; 289c3ab6167SBrent Vatne 290c3ab6167SBrent Vatneconst emitter = new EventEmitter(ExpoSettingsModule); 291c3ab6167SBrent Vatne 292c3ab6167SBrent Vatneexport type ThemeChangeEvent = { 293c3ab6167SBrent Vatne theme: string; 294c3ab6167SBrent Vatne}; 295c3ab6167SBrent Vatne 296c3ab6167SBrent Vatneexport function addThemeListener(listener: (event: ThemeChangeEvent) => void): Subscription { 297c3ab6167SBrent Vatne return emitter.addListener<ThemeChangeEvent>('onChangeTheme', listener); 298c3ab6167SBrent Vatne} 299c3ab6167SBrent Vatne 300c3ab6167SBrent Vatneexport function getTheme(): string { 301c3ab6167SBrent Vatne return ExpoSettingsModule.getTheme(); 302c3ab6167SBrent Vatne} 303c3ab6167SBrent Vatne 304c3ab6167SBrent Vatneexport function setTheme(theme: string): void { 305c3ab6167SBrent Vatne return ExpoSettingsModule.setTheme(theme); 306c3ab6167SBrent Vatne} 307c3ab6167SBrent Vatne``` 308c3ab6167SBrent Vatne 309c3ab6167SBrent Vatne### Example app 310c3ab6167SBrent Vatne 311da53c187SŁukasz Kosmaty```typescript example/App.tsx 312c3ab6167SBrent Vatneimport * as Settings from 'expo-settings'; 313c3ab6167SBrent Vatneimport * as React from 'react'; 314c3ab6167SBrent Vatneimport { Button, Text, View } from 'react-native'; 315c3ab6167SBrent Vatne 316c3ab6167SBrent Vatneexport default function App() { 317f2a036c6SBrent Vatne const [theme, setTheme] = React.useState<string>(Settings.getTheme()); 318c3ab6167SBrent Vatne 319c3ab6167SBrent Vatne React.useEffect(() => { 320c3ab6167SBrent Vatne const subscription = Settings.addThemeListener(({ theme: newTheme }) => { 321c3ab6167SBrent Vatne setTheme(newTheme); 322c3ab6167SBrent Vatne }); 323c3ab6167SBrent Vatne 324c3ab6167SBrent Vatne return () => subscription.remove(); 325c3ab6167SBrent Vatne }, [setTheme]); 326c3ab6167SBrent Vatne 327c3ab6167SBrent Vatne // Toggle between dark and light theme 328c3ab6167SBrent Vatne const nextTheme = theme === 'dark' ? 'light' : 'dark'; 329c3ab6167SBrent Vatne 330c3ab6167SBrent Vatne return ( 331c3ab6167SBrent Vatne <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}> 332c3ab6167SBrent Vatne <Text>Theme: {Settings.getTheme()}</Text> 333c3ab6167SBrent Vatne <Button title={`Set theme to ${nextTheme}`} onPress={() => Settings.setTheme(nextTheme)} /> 334c3ab6167SBrent Vatne </View> 335c3ab6167SBrent Vatne ); 336c3ab6167SBrent Vatne} 337c3ab6167SBrent Vatne``` 338c3ab6167SBrent Vatne 33930c3f1a5SBrent Vatne## 6. Improve type safety with Enums 340c3ab6167SBrent Vatne 341c3ab6167SBrent VatneIt's easy for us to make a mistake when using the `Settings.setTheme()` API in its current form, because we can set the theme to any string value. We can improve the type safety of this API by using an enum to restrict the possible values to `system`, `light`, and `dark`. 342c3ab6167SBrent Vatne 343c3ab6167SBrent Vatne### iOS native module 344c3ab6167SBrent Vatne 345c3ab6167SBrent Vatne```swift ios/ExpoSettingsModule.swift 346c3ab6167SBrent Vatneimport ExpoModulesCore 347c3ab6167SBrent Vatne 348c3ab6167SBrent Vatnepublic class ExpoSettingsModule: Module { 349c3ab6167SBrent Vatne public func definition() -> ModuleDefinition { 350c3ab6167SBrent Vatne Name("ExpoSettings") 351c3ab6167SBrent Vatne 352c3ab6167SBrent Vatne Events("onChangeTheme") 353c3ab6167SBrent Vatne 354c3ab6167SBrent Vatne Function("setTheme") { (theme: Theme) -> Void in 355c3ab6167SBrent Vatne UserDefaults.standard.set(theme.rawValue, forKey:"theme") 356c3ab6167SBrent Vatne sendEvent("onChangeTheme", [ 357c3ab6167SBrent Vatne "theme": theme.rawValue 358c3ab6167SBrent Vatne ]) 359c3ab6167SBrent Vatne } 360c3ab6167SBrent Vatne 361c3ab6167SBrent Vatne Function("getTheme") { () -> String in 362c3ab6167SBrent Vatne UserDefaults.standard.string(forKey: "theme") ?? Theme.system.rawValue 363c3ab6167SBrent Vatne } 364c3ab6167SBrent Vatne } 365c3ab6167SBrent Vatne 366c3ab6167SBrent Vatne enum Theme: String, Enumerable { 367c3ab6167SBrent Vatne case light 368c3ab6167SBrent Vatne case dark 369c3ab6167SBrent Vatne case system 370c3ab6167SBrent Vatne } 371c3ab6167SBrent Vatne} 372c3ab6167SBrent Vatne``` 373c3ab6167SBrent Vatne 374c3ab6167SBrent Vatne### Android native module 375c3ab6167SBrent Vatne 376b06088cdSBrent Vatne```kotlin android/src/main/java/expo/modules/settings/ExpoSettingsModule.kt 377c3ab6167SBrent Vatnepackage expo.modules.settings 378c3ab6167SBrent Vatne 379c3ab6167SBrent Vatneimport android.content.Context 380c3ab6167SBrent Vatneimport android.content.SharedPreferences 381c3ab6167SBrent Vatneimport androidx.core.os.bundleOf 382c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.Module 383c3ab6167SBrent Vatneimport expo.modules.kotlin.modules.ModuleDefinition 384afab1fd9SDaniel Friyiaimport expo.modules.kotlin.types.Enumerable 385c3ab6167SBrent Vatne 386c3ab6167SBrent Vatneclass ExpoSettingsModule : Module() { 387c3ab6167SBrent Vatne override fun definition() = ModuleDefinition { 388c3ab6167SBrent Vatne Name("ExpoSettings") 389c3ab6167SBrent Vatne 390c3ab6167SBrent Vatne Events("onChangeTheme") 391c3ab6167SBrent Vatne 392c3ab6167SBrent Vatne Function("setTheme") { theme: Theme -> 393c3ab6167SBrent Vatne getPreferences().edit().putString("theme", theme.value).commit() 394c3ab6167SBrent Vatne [email protected]("onChangeTheme", bundleOf("theme" to theme.value)) 395c3ab6167SBrent Vatne } 396c3ab6167SBrent Vatne 397c3ab6167SBrent Vatne Function("getTheme") { 398c3ab6167SBrent Vatne return@Function getPreferences().getString("theme", Theme.SYSTEM.value) 399c3ab6167SBrent Vatne } 400c3ab6167SBrent Vatne } 401c3ab6167SBrent Vatne 402c3ab6167SBrent Vatne private val context 403c3ab6167SBrent Vatne get() = requireNotNull(appContext.reactContext) 404c3ab6167SBrent Vatne 405c3ab6167SBrent Vatne private fun getPreferences(): SharedPreferences { 406c3ab6167SBrent Vatne return context.getSharedPreferences(context.packageName + ".settings", Context.MODE_PRIVATE) 407c3ab6167SBrent Vatne } 408c3ab6167SBrent Vatne} 409c3ab6167SBrent Vatne 410c3ab6167SBrent Vatneenum class Theme(val value: String) : Enumerable { 411c3ab6167SBrent Vatne LIGHT("light"), 412c3ab6167SBrent Vatne DARK("dark"), 413c3ab6167SBrent Vatne SYSTEM("system") 414c3ab6167SBrent Vatne} 415c3ab6167SBrent Vatne``` 416c3ab6167SBrent Vatne 417c3ab6167SBrent Vatne### TypeScript module 418c3ab6167SBrent Vatne 419da53c187SŁukasz Kosmaty```typescript src/index.ts 420da53c187SŁukasz Kosmatyimport { EventEmitter, Subscription } from 'expo-modules-core'; 421c3ab6167SBrent Vatne 422da53c187SŁukasz Kosmatyimport ExpoSettingsModule from './ExpoSettingsModule'; 423c3ab6167SBrent Vatne 424c3ab6167SBrent Vatneconst emitter = new EventEmitter(ExpoSettingsModule); 425c3ab6167SBrent Vatne 426da53c187SŁukasz Kosmatyexport type Theme = 'light' | 'dark' | 'system'; 427c3ab6167SBrent Vatne 428c3ab6167SBrent Vatneexport type ThemeChangeEvent = { 429c3ab6167SBrent Vatne theme: Theme; 430c3ab6167SBrent Vatne}; 431c3ab6167SBrent Vatne 432da53c187SŁukasz Kosmatyexport function addThemeListener(listener: (event: ThemeChangeEvent) => void): Subscription { 433da53c187SŁukasz Kosmaty return emitter.addListener<ThemeChangeEvent>('onChangeTheme', listener); 434c3ab6167SBrent Vatne} 435c3ab6167SBrent Vatne 436c3ab6167SBrent Vatneexport function getTheme(): Theme { 437c3ab6167SBrent Vatne return ExpoSettingsModule.getTheme(); 438c3ab6167SBrent Vatne} 439c3ab6167SBrent Vatne 440c3ab6167SBrent Vatneexport function setTheme(theme: Theme): void { 441c3ab6167SBrent Vatne return ExpoSettingsModule.setTheme(theme); 442c3ab6167SBrent Vatne} 443c3ab6167SBrent Vatne``` 444c3ab6167SBrent Vatne 445c3ab6167SBrent Vatne### Example app 446c3ab6167SBrent Vatne 447c3ab6167SBrent VatneIf we change `Settings.setTheme(nextTheme)` to `Settings.setTheme("not-a-real-theme")`, TypeScript will complain, and if we ignore that and go ahead and press the button anyways, we'll see the following error: 448c3ab6167SBrent Vatne 449c3ab6167SBrent Vatne``` 450c3ab6167SBrent Vatne ERROR Error: FunctionCallException: Calling the 'setTheme' function has failed (at ExpoModulesCore/SyncFunctionComponent.swift:76) 451c3ab6167SBrent Vatne→ Caused by: ArgumentCastException: Argument at index '0' couldn't be cast to type Enum<Theme> (at ExpoModulesCore/JavaScriptUtils.swift:41) 452c3ab6167SBrent Vatne→ Caused by: EnumNoSuchValueException: 'not-a-real-theme' is not present in Theme enum, it must be one of: 'light', 'dark', 'system' (at ExpoModulesCore/Enumerable.swift:37) 453c3ab6167SBrent Vatne``` 454c3ab6167SBrent Vatne 455c3ab6167SBrent VatneWe can see from the last line of the error message that `not-a-real-theme` is not a valid value for the `Theme` enum, and that `light`, `dark`, and `system` are the only valid values. 456c3ab6167SBrent Vatne 457c3ab6167SBrent Vatne## Next steps 458c3ab6167SBrent Vatne 459*cfa99e0fSAman MittalCongratulations! You have created your first simple yet non-trivial Expo module for Android and iOS. 4609ec76667SBrent Vatne 461*cfa99e0fSAman MittalYou can con continue to the next tutorial to learn how to create a native view or see the Expo module API reference. 462*cfa99e0fSAman Mittal 463*cfa99e0fSAman Mittal<BoxLink 464*cfa99e0fSAman Mittal title="Creating a native view" 465*cfa99e0fSAman Mittal description="A tutorial on creating a native view that renders a WebView with Expo modules API." 466*cfa99e0fSAman Mittal href="/modules/native-view-tutorial/" 467*cfa99e0fSAman Mittal Icon={BookOpen02Icon} 468*cfa99e0fSAman Mittal/> 469*cfa99e0fSAman Mittal 470*cfa99e0fSAman Mittal<BoxLink 471*cfa99e0fSAman Mittal title="Module API Reference" 472*cfa99e0fSAman Mittal description="Outline for the Expo Module API and common patterns like sending events from native code to JavaScript." 473*cfa99e0fSAman Mittal href="/modules/module-api/" 474*cfa99e0fSAman Mittal Icon={Grid01Icon} 475*cfa99e0fSAman Mittal/> 476