summaryrefslogtreecommitdiff
path: root/app_expo/models
diff options
context:
space:
mode:
Diffstat (limited to 'app_expo/models')
-rw-r--r--app_expo/models/character-store/character-store.test.ts7
-rw-r--r--app_expo/models/character-store/character-store.ts37
-rw-r--r--app_expo/models/character/character.test.ts10
-rw-r--r--app_expo/models/character/character.ts17
-rw-r--r--app_expo/models/environment.ts40
-rw-r--r--app_expo/models/extensions/with-environment.ts17
-rw-r--r--app_expo/models/extensions/with-root-store.ts17
-rw-r--r--app_expo/models/index.ts5
-rw-r--r--app_expo/models/root-store/root-store-context.ts22
-rw-r--r--app_expo/models/root-store/root-store.ts20
-rw-r--r--app_expo/models/root-store/setup-root-store.ts55
11 files changed, 247 insertions, 0 deletions
diff --git a/app_expo/models/character-store/character-store.test.ts b/app_expo/models/character-store/character-store.test.ts
new file mode 100644
index 0000000..fc17694
--- /dev/null
+++ b/app_expo/models/character-store/character-store.test.ts
@@ -0,0 +1,7 @@
+import { CharacterStoreModel } from './character-store'
+
+test('can be created', () => {
+ const instance = CharacterStoreModel.create({})
+
+ expect(instance).toBeTruthy()
+})
diff --git a/app_expo/models/character-store/character-store.ts b/app_expo/models/character-store/character-store.ts
new file mode 100644
index 0000000..9751118
--- /dev/null
+++ b/app_expo/models/character-store/character-store.ts
@@ -0,0 +1,37 @@
+import { Instance, SnapshotOut, types } from 'mobx-state-tree'
+import { CharacterModel, CharacterSnapshot } from '../character/character'
+import { CharacterApi } from '../../services/api/character-api'
+import { withEnvironment } from '../extensions/with-environment'
+
+/**
+ * Example store containing Rick and Morty characters
+ */
+export const CharacterStoreModel = types
+ .model('CharacterStore')
+ .props({
+ characters: types.optional(types.array(CharacterModel), []),
+ })
+ .extend(withEnvironment)
+ .actions((self) => ({
+ saveCharacters: (characterSnapshots: CharacterSnapshot[]) => {
+ self.characters.replace(characterSnapshots)
+ },
+ }))
+ .actions((self) => ({
+ getCharacters: async () => {
+ const characterApi = new CharacterApi(self.environment.api)
+ const result = await characterApi.getCharacters()
+
+ if (result.kind === 'ok') {
+ self.saveCharacters(result.characters)
+ } else {
+ __DEV__ && console.tron.log(result.kind)
+ }
+ },
+ }))
+
+type CharacterStoreType = Instance<typeof CharacterStoreModel>
+export interface CharacterStore extends CharacterStoreType {}
+type CharacterStoreSnapshotType = SnapshotOut<typeof CharacterStoreModel>
+export interface CharacterStoreSnapshot extends CharacterStoreSnapshotType {}
+export const createCharacterStoreDefaultModel = () => types.optional(CharacterStoreModel, {})
diff --git a/app_expo/models/character/character.test.ts b/app_expo/models/character/character.test.ts
new file mode 100644
index 0000000..d7bfac7
--- /dev/null
+++ b/app_expo/models/character/character.test.ts
@@ -0,0 +1,10 @@
+import { CharacterModel } from './character'
+
+test('can be created', () => {
+ const instance = CharacterModel.create({
+ id: 1,
+ name: 'Rick Sanchez',
+ })
+
+ expect(instance).toBeTruthy()
+})
diff --git a/app_expo/models/character/character.ts b/app_expo/models/character/character.ts
new file mode 100644
index 0000000..4405338
--- /dev/null
+++ b/app_expo/models/character/character.ts
@@ -0,0 +1,17 @@
+import { Instance, SnapshotOut, types } from 'mobx-state-tree'
+
+/**
+ * Rick and Morty character model.
+ */
+export const CharacterModel = types.model('Character').props({
+ id: types.identifierNumber,
+ name: types.maybe(types.string),
+ status: types.maybe(types.string),
+ image: types.maybe(types.string),
+})
+
+type CharacterType = Instance<typeof CharacterModel>
+export interface Character extends CharacterType {}
+type CharacterSnapshotType = SnapshotOut<typeof CharacterModel>
+export interface CharacterSnapshot extends CharacterSnapshotType {}
+export const createCharacterDefaultModel = () => types.optional(CharacterModel, {})
diff --git a/app_expo/models/environment.ts b/app_expo/models/environment.ts
new file mode 100644
index 0000000..a29586b
--- /dev/null
+++ b/app_expo/models/environment.ts
@@ -0,0 +1,40 @@
+import { Api } from '../services/api'
+
+let ReactotronDev
+if (__DEV__) {
+ const { Reactotron } = require('../services/reactotron')
+ ReactotronDev = Reactotron
+}
+
+/**
+ * The environment is a place where services and shared dependencies between
+ * models live. They are made available to every model via dependency injection.
+ */
+export class Environment {
+ constructor() {
+ // create each service
+ if (__DEV__) {
+ // dev-only services
+ this.reactotron = new ReactotronDev()
+ }
+ this.api = new Api()
+ }
+
+ async setup() {
+ // allow each service to setup
+ if (__DEV__) {
+ await this.reactotron.setup()
+ }
+ await this.api.setup()
+ }
+
+ /**
+ * Reactotron is only available in dev.
+ */
+ reactotron: typeof ReactotronDev
+
+ /**
+ * Our api.
+ */
+ api: Api
+}
diff --git a/app_expo/models/extensions/with-environment.ts b/app_expo/models/extensions/with-environment.ts
new file mode 100644
index 0000000..1fc190e
--- /dev/null
+++ b/app_expo/models/extensions/with-environment.ts
@@ -0,0 +1,17 @@
+import { getEnv, IStateTreeNode } from 'mobx-state-tree'
+import { Environment } from '../environment'
+
+/**
+ * Adds a environment property to the node for accessing our
+ * Environment in strongly typed.
+ */
+export const withEnvironment = (self: IStateTreeNode) => ({
+ views: {
+ /**
+ * The environment.
+ */
+ get environment() {
+ return getEnv<Environment>(self)
+ },
+ },
+})
diff --git a/app_expo/models/extensions/with-root-store.ts b/app_expo/models/extensions/with-root-store.ts
new file mode 100644
index 0000000..72ab478
--- /dev/null
+++ b/app_expo/models/extensions/with-root-store.ts
@@ -0,0 +1,17 @@
+import { getRoot, IStateTreeNode } from 'mobx-state-tree'
+import { RootStoreModel } from '../root-store/root-store'
+
+/**
+ * Adds a rootStore property to the node for a convenient
+ * and strongly typed way for stores to access other stores.
+ */
+export const withRootStore = (self: IStateTreeNode) => ({
+ views: {
+ /**
+ * The root store.
+ */
+ get rootStore() {
+ return getRoot<typeof RootStoreModel>(self)
+ },
+ },
+})
diff --git a/app_expo/models/index.ts b/app_expo/models/index.ts
new file mode 100644
index 0000000..74909fc
--- /dev/null
+++ b/app_expo/models/index.ts
@@ -0,0 +1,5 @@
+export * from './extensions/with-environment'
+export * from './extensions/with-root-store'
+export * from './root-store/root-store'
+export * from './root-store/root-store-context'
+export * from './root-store/setup-root-store'
diff --git a/app_expo/models/root-store/root-store-context.ts b/app_expo/models/root-store/root-store-context.ts
new file mode 100644
index 0000000..f01a35c
--- /dev/null
+++ b/app_expo/models/root-store/root-store-context.ts
@@ -0,0 +1,22 @@
+import { createContext, useContext } from 'react'
+import { RootStore } from './root-store'
+
+/**
+ * Create a context we can use to
+ * - Provide access to our stores from our root component
+ * - Consume stores in our screens (or other components, though it's
+ * preferable to just connect screens)
+ */
+const RootStoreContext = createContext<RootStore>({} as RootStore)
+
+/**
+ * The provider our root component will use to expose the root store
+ */
+export const RootStoreProvider = RootStoreContext.Provider
+
+/**
+ * A hook that screens can use to gain access to our stores, with
+ * `const { someStore, someOtherStore } = useStores()`,
+ * or less likely: `const rootStore = useStores()`
+ */
+export const useStores = () => useContext(RootStoreContext)
diff --git a/app_expo/models/root-store/root-store.ts b/app_expo/models/root-store/root-store.ts
new file mode 100644
index 0000000..56425bc
--- /dev/null
+++ b/app_expo/models/root-store/root-store.ts
@@ -0,0 +1,20 @@
+import { Instance, SnapshotOut, types } from 'mobx-state-tree'
+import { CharacterStoreModel } from '../character-store/character-store'
+
+/**
+ * A RootStore model.
+ */
+// prettier-ignore
+export const RootStoreModel = types.model("RootStore").props({
+ characterStore: types.optional(CharacterStoreModel, {} as any),
+})
+
+/**
+ * The RootStore instance.
+ */
+export interface RootStore extends Instance<typeof RootStoreModel> {}
+
+/**
+ * The data of a RootStore.
+ */
+export interface RootStoreSnapshot extends SnapshotOut<typeof RootStoreModel> {}
diff --git a/app_expo/models/root-store/setup-root-store.ts b/app_expo/models/root-store/setup-root-store.ts
new file mode 100644
index 0000000..40e3ad8
--- /dev/null
+++ b/app_expo/models/root-store/setup-root-store.ts
@@ -0,0 +1,55 @@
+import { onSnapshot } from 'mobx-state-tree'
+import { RootStoreModel, RootStore } from './root-store'
+import { Environment } from '../environment'
+import * as storage from '../../utils/storage'
+
+/**
+ * The key we'll be saving our state as within async storage.
+ */
+const ROOT_STATE_STORAGE_KEY = 'root'
+
+/**
+ * Setup the environment that all the models will be sharing.
+ *
+ * The environment includes other functions that will be picked from some
+ * of the models that get created later. This is how we loosly couple things
+ * like events between models.
+ */
+export async function createEnvironment() {
+ const env = new Environment()
+ await env.setup()
+ return env
+}
+
+/**
+ * Setup the root state.
+ */
+export async function setupRootStore() {
+ let rootStore: RootStore
+ let data: any
+
+ // prepare the environment that will be associated with the RootStore.
+ const env = await createEnvironment()
+ try {
+ // load data from storage
+ data = (await storage.load(ROOT_STATE_STORAGE_KEY)) || {}
+ rootStore = RootStoreModel.create(data, env)
+ } catch (e) {
+ // if there's any problems loading, then let's at least fallback to an empty state
+ // instead of crashing.
+ rootStore = RootStoreModel.create({}, env)
+
+ // but please inform us what happened
+ __DEV__ && console.tron.error(e.message, null)
+ }
+
+ // reactotron logging
+ if (__DEV__) {
+ env.reactotron.setRootStore(rootStore, data)
+ }
+
+ // track changes & save to storage
+ onSnapshot(rootStore, (snapshot) => storage.save(ROOT_STATE_STORAGE_KEY, snapshot))
+
+ return rootStore
+}