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