diff options
Diffstat (limited to 'app_expo/models')
-rw-r--r-- | app_expo/models/character-store/character-store.test.ts | 7 | ||||
-rw-r--r-- | app_expo/models/character-store/character-store.ts | 37 | ||||
-rw-r--r-- | app_expo/models/character/character.test.ts | 10 | ||||
-rw-r--r-- | app_expo/models/character/character.ts | 17 | ||||
-rw-r--r-- | app_expo/models/environment.ts | 40 | ||||
-rw-r--r-- | app_expo/models/extensions/with-environment.ts | 17 | ||||
-rw-r--r-- | app_expo/models/extensions/with-root-store.ts | 17 | ||||
-rw-r--r-- | app_expo/models/index.ts | 5 | ||||
-rw-r--r-- | app_expo/models/root-store/root-store-context.ts | 22 | ||||
-rw-r--r-- | app_expo/models/root-store/root-store.ts | 20 | ||||
-rw-r--r-- | app_expo/models/root-store/setup-root-store.ts | 55 |
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 +} |