もやもやエンジニア

IT系のネタで思ったことや技術系のネタを備忘録的に綴っていきます。フロント率高め。

ちはやふるにはまったので百人一首を暗記するアプリ作った (2回目)

前回のリリース

rei19.hatenablog.com

今回作ったもの

play.google.com

前回作ったアプリはいまだにちょこちょこ使われてるのですが、リリース当初から「歌の読み上げ機能がない」「読み上げがあれば最高!」というレビューが来ていて、さらに3年経ってもいまだに飛んてくるので需要に答える形で作ることにしました。仕事はWebの開発ばかりだし、そろそろAndroidアプリの仕事もしたいし、リハビリがてら丁度いいかなあという思いです。

元のアプリはライトユーザーを想定して作っているので、今回は想定するユーザーを「ちょっとだけ競技かるたに興味のある人」にして、新しいアプリとして作りました。

技術的な話

  • 特段特別なことはしておらず、元のアプリから使えるモジュールは使いまわしつつ、DBレイヤーはOrma -> Roomに変更、全体の構成はNavigationComponentをベースにしたシングルActivity構成にしたりしました。

技術以外の話

  • そもそも読み上げに使える歌の音源が見つからなかったので、Twitter経由で読み手をやっている方に相談してみたり(ke_ko26さんありがとうございました)、かるた協会に問い合わせたりしたもののやはり解決しなかったので、ココナラを使って自分で発注をしてデータを作成しました。出品者側で使ったことはあったものの、依頼者側としては初めて利用しましたがスムーズにやり取りできました。

おわり

  • 久しぶりにアプリ作りましたが、Androidは公式のドキュメントやCodeLabが充実してるのでキャッチアップしやすくていいですね。仕事でAndroid触らなくなって久しいですが書いてて楽しいです。

コード

github.com

最後に宣伝

www.ntv.co.jp

chihayafund.com

リモートワーク環境の改善のためにFLEXISPOTの電動昇降デスクを買った

前段

しばらくリモートワークが続きそうな雰囲気なので、仕事の効率を上げるためにある程度の投資は必要かなと思い色々買い揃えることにしました。新コロ騒ぎ以前はリビングで仕事してたんですが、その前提条件は "子供が保育園に行っていること" であり、緊急事態宣言下で保育園が使えなくなって常時子供が家にいる状態ではさすがに同じクオリティでアウトプットをだせないかなーと。

幸い住んでいるところは1軒家で、将来は子供部屋にする予定の部屋が空いていたので、そこを作業部屋に作り変えるプランにして、まずは机を買おうということで色々検討しました。要件としてはスタンディングでの作業ができるのが必須で後は見た目がごちゃごちゃしていないくらいです。

買ったもの

公式はこちら https://flexispot.jp/flexispot-e6.html

前職で一緒だったwadapさんのnoteで紹介されてたものが一番イメージに近かったので、FLEXISPOTのE6というのにしました。信頼できる人がおすすめしてるという安心感もあります。

note.com

pros

  • イメージ通りのシンプルな見た目でよい
  • 組み立てが簡単
  • 操作が簡単。高さは任意の位置を登録できて(3パターン)、登録すればワンタッチで高さを切り替えることができる

cons

  • かなり重い。配置予定の部屋は玄関近くの部屋で搬入は苦労しなかったのですが、もし2階に置くとかだと僕の体格ではかなりきつかったと思います。
  • あまり本品のレビューではないですが、公式サイトの「特典新規会員登録で「5%offクーポン」プレゼント」につられて公式で注文したのですが、注文から発送まで2週間近くかかりました。その経緯も注文から2週間たってもステータスが変わらなかったので、いつごろ発送されるかくらい教えてくれない?と問い合わせてみたら、なぜか問い合わせの返事ではなく発送のお知らせが来てそのあとすぐにモノが届きました。

おしまい

コロナ禍を機に自宅の作業環境を整えようかなーという人がいたらおすすめの机です。

Material DesignのDark themeの章読んだめも

dark-themeのページを流し読みしたメモ。

material.io

めも

  • 完全な黒ではなくdark gray(おすすめは#121212らしい)を使用する。
  • UI Componentにはaccent colorを使う。
  • 暗い色を基調とし、明るいピクセルを減らす(OLEDスクリーンを使ったデバイスなどにおいては端末のバッテリー消費が抑えられる)
  • 背景色とテキストの色のコントラストは指定のガイドラインを満たすようにする。ガイドラインは↓

www.w3.org

  • dark themeはアプリ内のコントロールでdefaultのthemeと切り替えることができる。実装のパターンは固定ではない
  • elevationが高いほど明るめのdark grayをsurfaceに使う。明るさはelevationに応じた半透明のoverlay(0 〜 16%)を被せて調整する。
  • surfaceにaccent colorを使った場合はshadowでelevationを表現する。

2019年振り返り

最近は職場以外の人とあまり会ってないので生存報告がてら振り返りなど

仕事の開発

  • 淡々とスタートアップぽい開発してB2Bサービス作ってます。構成もバックエンドはRoR、フロントエンドはReact/TypeScriptでスタートアップっぽい。ReactはFunctionComponent + Hooksの構成に書き換えていてまあまあ見通しのいいコードになったかなと感じてます。
  • バックエンドもフロントエンドも触ってますが、フロントについてはJoinしてからアーキテクチャの整理をして、こつこつリファクタをした結果、Sentryに飛んでくるUnhandledなエラーがほぼ無くなったので、そのへんは成果を出せたかなと思ってます。テストも無い状態から結構頑張った。

個人の開発

  • Firebaseを使ったサービスを作りました。銀の弾丸というわけじゃないのですが、せっかくなので仕事で使えそうな機会があったら取り入れてみたいお気持ちです。
  • 3年前にリリースした百人一首のアプリが20万DLに届いたので純粋にうれしいですね。
  • 今年はWeb成分多めだったので来年はAndroidのアプリをなにか作りたいということで、復習がてらGoogleのCodelabをもくもくとやってました。

その他

  • 来年で現職について3年になるので一旦どこかで振り返りしようかなと思ってます。LAPRAS経由でたまにスカウトが来ますが、転職志望は直近でどうのこうのというのはないけれどそれでもよければという体で話を聞いて、市場の雰囲気や自分のスキルセットの市場価値みたいなところを意識するようにしてます。
  • 去年から引き続き子供優先の生活をしているので、勉強する時間や個人サービスを作る時間がほぼ深夜になるのがまあまあ厳しいですね。先達の方々はどう折り合いつけてるのか。。。

React(Gatsby)+ FirebaseでWebサービス作った

作ったやつ

川柳投稿するだけのサービス

senryu.app

目的

毎年なにかしら新しい言語とか技術を覚えてアウトプットするというのを続けていて、今年はあまり使ったことがなかったFirebaseメインでなにか作ってみるかということで、認証+コンテンツのCRUDがあるスタンダードなWebサービスを作りました。今回はFirebaseを使うというのが目的なので、使ってもらえそうかどうかというのはあまり考えずに作ってます。よく新しい技術覚えるときにTODOリスト作ったりしますが、簡単なサンプル作っただけだと出来た気になるだけであんまりよくないと思っているので、ちゃんとそれっぽいサービスを作るようにしてます。

技術的な話

Firebaseで○○を作ってみた系の記事はたくさんあるので、あまり言及することもないのですが、設計的なところだけ少し。Presentation層はできるだけFirebaseの存在を意識しないように作っています(ただFirebaseのコンテキストにおいてはこの方針がベターかはわからないです。FirebaseUIを使っている関係上、認証部分にはFirebaseが漏れていたりするので)。具体的なコードのイメージは以下のとおりです。

ドメイン層を作ってリポジトリのIFを配置

src/domain/repositories/index.ts

export interface SenryuRepository {
  findById(id: SenryuId): Promise<Senryu>;
  findByUserPerPage(
    userId: UserId,
    pageNo: number,
    base?: Senryu
  ): Promise<Page<Senryu>>;
  findAllPerPage(pageNo: number, base?: Senryu): Promise<Page<Senryu>>;
  add(senryu: SenryuDraft): Promise<SenryuId>;
  delete(senryuId: SenryuId): Promise<void>;
}

インフラ層でリポジトリを実装

src/infrastructure/repositories/SenryuRepositoryData.ts

export class SenryuRepositoryData implements SenryuRepository {
  async findById(id: SenryuId) {
    try {
      const snap = await senryuCollection()
        .doc(id)
        .get();
      const data = snap.data();
      if (data !== undefined) {
        return dataToModel(id, data);
      } else {
        throw reasonToAppError({ code: 'not-found' }, '川柳');
      }
    } catch (reason) {
      throw reasonToAppError(reason, '川柳');
    }
  }

  // 中略
}

ReactのContextを使ってRepositoryの実装をDIできるようにする

src/contexts/DiContainerContextProvider.tsx

export const defalutValue = {
  senryuRepository: new SenryuRepositoryData(), // ここで実体を作成
};

export const DiContainerContext = React.createContext<{
  senryuRepository: SenryuRepository;
}>(defalutValue);

const DiContainerContextProvider: React.FC<{}> = ({ children }) => (
  <DiContainerContext.Provider value={defalutValue}>
    {children}
  </DiContainerContext.Provider>
);

src/hooks/useDiContainer.ts

import { useContext } from 'react';
import { DiContainerContext } from '@src/contexts/DiContainerContextProvider';

export const useDiContainer = () => useContext(DiContainerContext);

custom hookでContextからRepositoryをDIする

src/hooks/useSenryuList.ts

type Deps = {
  senryuRepository: SenryuRepository;
};

export const useSenryuList = (
  { senryuRepository }: Deps = useDiContainer()
): Return => {
  // 中略
}

hookのテストはRepositoryのモックで差し替えることができる

const genMockSenryuRepository = () =>
  jest.genMockFromModule<SenryuRepository>('@src/domain/repositories');

const senryuRepository = genMockSenryuRepository();
senryuRepository.findAllPerPage = jest.fn(pageNo => {
   // テスト用のデータをPromiseで返す
});

const { result, waitForNextUpdate } = renderHook(() =>
    useSenryuList({ senryuRepository })
);

という感じでFirebaseはRepositoryの実装に封印する形で作っています。これは普段RESTなAPIを使うときにやっている実装で、そこをFirebaseに置き換えた体ですね。とはいえ先にもコメントしたとおり、Firebase使う場合はこの形がよいのかはまだわかってないので、Firebaseと一蓮托生する形で設計した方がいいかもわからんです。

おしまい

  • Gatsbyである必要全く無いのですが、最近遊んでたというだけの理由で使ってます。
  • 大きな残件としてFirebaseにからむところのテストの書き方を調べきれてないので、そのあたりまでカバーして、デザインとかSEOまわりを見直したらいったんこれの開発は終わりにする予定
  • ドメインを初めてGoogle Domainsで取ったのですが、普段使ってるお名前.comよりだいぶ使いやすかったです。
  • いつも通りコードはポートフォリオ代わりに公開してます。

静岡の用宗というところで開発合宿したら最の高だった

定期的に個人でもくもく開発するのが好きなメンバーで開発合宿に行ってるのですが、今回使ったところはなかなか良い体験でしたので紹介など。

場所

静岡は用宗という港町にある「日本色」という一棟貸しの宿を使いました。部屋は定員7人の撫子を借りてます。小さいチームの開発合宿としては丁度いいサイズですね。

nihoniro.jp

部屋はここ nihoniro.jp

開発合宿は何回か開催しているのですが、選ぶポイントは以下の条件を満たせるかで選んでいます。

  • 1軒家を借りられる。周りに気を使わなくて良いのでおすすめ。
  • 2泊3日のスケジュールを抑えられる。経験上1泊だと足りない。
  • wifiが使える。必須
  • 食事に不自由しなさそう。開発外の楽しみですね。

今回は全部合致したのでここを選びました。料金は定員の7名Maxで借りれば一人あたり1泊1万ちょいというところです。今回は7人揃わず6人だったのでちょっと割高。

おすすめポイント

東京から近い

  • 東京駅から新幹線で1時間ちょいでいける距離感です。僕は途中の熱海に寄りたかったので鈍行で海を見ながらのんびり行きました。

食べ物が美味しい

  • 港町なので基本的に魚介類が安くてすごい美味しいです。名物は生しらすで漁が行われていれば新鮮なやつをいただけます。↓は実際に食したやつ。

f:id:Rei19:20190908222757j:plain
海鮮丼

f:id:Rei19:20190908222911j:plain
生しらす

温泉と食事するポイントがまとまっている

  • 宿の近くに用宗みなと温泉という温泉と食事処が複数入っている用宗みなと横丁という施設があります。この温泉は中にも食事処とビアバーが揃っているので戦闘力が高いです。なお、この2つの施設の運営は宿の管理会社と同じなので、宿を予約すると温泉のチケットが付いてきて無料で入浴できます。

minato-onsen.jp

宿のサービスが良い

  • 駅の送迎だけでなく↑の温泉や食事処までの移動も気軽に受けてくれました。移動はトゥクトゥクを使うのですが、夏は風がすごい気持ちよくて良い体験でした。コンビニで買い出ししてから戻りたいですみたいなオーダーも気軽に受けてくれて助かりました。

f:id:Rei19:20190908224330j:plain
トゥクトゥク

おしまい

  • 人もあまり多くなく昔ながらの漁師町という印象で静かで作業に集中できてよかったです。宿も温泉もまだ新しい施設で快適に過ごせました。当日はカンカンに晴れていて富士山も望めて爽快でした。(最終日は台風15号から逃げるように帰りましたが)
  • 一応ちゃんと開発してたぞってことで、githubリポジトリなど(Firebaseちゃんと使ったことなかったのでFirebase使ったSPA作ってました。未完なので継続して開発)

GitHub - rei-m/web-senryu: 川柳投稿するサイト(を作っている)

Material-uiのstyled-components-apiを使う

今までstyled-componentsmaterial-uiそれぞれ入れて使ってたのですが、material-uiにstyled-componentっぽいAPIが生えてたのに気づいたので使ってみたメモ(キャッチアップできてないだけですね。。。)。@material-ui/coreのバージョンは4.1.0です。

リファレンス

material-ui.com

使ってみる

基本

公式に載ってるコードだとこんな感じですね。本家の方はTemplateStringsArrayでスタイル定義しますが、こちらはデフォルトだとCSSPropertiesのObjectを食わすのでよりJSっぽいスタイルです。

import React from 'react';
import { styled } from '@material-ui/styles';
import Button from '@material-ui/core/Button';

const MyButton = styled(Button)({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  border: 0,
  borderRadius: 3,
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  color: 'white',
  height: 48,
  padding: '0 30px',
});

MUIのComponentではなく普通のHTMLのタグを指定したいときはstringを指定します。

import React from 'react';
import { styled } from '@material-ui/styles';

const MyButton = styled('button')({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  border: 0,
  borderRadius: 3,
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  color: 'white',
  height: 48,
  padding: '0 30px',
});

Propsに応じてスタイルを変える

Propsに応じてスタイルを変えたい時はこんな感じです。Componentは必要なPropsを渡せる形にして、スタイルは値ではなくpropsを受け取って値を返す関数を設定します。

import React from 'react';
import { styled } from '@material-ui/styles';

type MyButtonProps = {
  size: 'small' | 'normal';
};

// lintで怒られるの不本意なんですけど・・・
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const MyButton = styled(({ size, ...other }: MyButtonProps) => <button {...other} />)({
  background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
  border: 0,
  borderRadius: 3,
  boxShadow: '0 3px 5px 2px rgba(255, 105, 135, .3)',
  color: 'white',
  height: 48,
  padding: '0 30px',
  fontSize: ({ size }: MyButtonProps) => (size === 'small' ? '12px' : '16px'),
});

自分で定義したComponentで使う

classNameをPropsで渡してRootのComponentに渡します。

MyComponent.tsx

import React from 'react';
import { styled } from '@material-ui/styles';

type MyComponentProps = {
  className?: string;
};

const MyComponent = ({ className }: MyComponentProps) => (
  <div className={className}>こんぽーねんと</div>
);

export default MyComponent;

CustomComponent.tsx

import React from 'react';
import { styled } from '@material-ui/styles';
import MyComponent from './MyComponent';

const StyledMyComponent = styled(MyComponent)({
 background: 'linear-gradient(45deg, #FE6B8B 30%, #FF8E53 90%)',
});

const CustomComponent = () => (
  <div><StyledMyComponent /></div>
);

export default CustomComponent;

おしまい

  • 少し試しただけですが最初から作るならMaterialUIに寄せてもいいかなと思いました。