Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
150
Help us understand the problem. What are the problem?

More than 3 years have passed since last update.

あとで読むQiitaリーダーアプリをリリースしました

:tada:Qiita Pocketというiphoneアプリを先日リリースしました:iphone:

どんなアプリ?

Qiitaの気になる記事をローカルに保存しスキマ時間に閲覧できる "あとで読む" Qiitaリーダーアプリです。

APP Store
https://appsto.re/jp/yLaTib.i
GitHub
https://github.com/hirothings/qiita-pocket
で開発しています。プルリク頂けると嬉しいです

なぜ作ったか?

「イイね」に対する疑問 :thinking:

Qiitaに「イイね」が導入されたときの運営ブログで、
ストックに「後で読もう」と「素晴らしい記事を見つけた」が混在しているため、
純粋に「イイね」というフィードバックを送る「イイね」を導入したとあります。

が、リリース後もストックに「後で読む」と「素晴らしい記事」が混在する状況は変わりませんでした。
個人的には「イイね」 押す手間が増えただけで、厳密に評価しなくとも「後で読む」も評価に入れたらいいのでは?と思いました。
というより「後で読む」機能があればいいのでは?と思い、自分で作ろうと思いました。

開発期間

コアで開発した期間は2ヶ月ほどですが、取り掛かったのは昨年の9月とかで
完全に中だるみしました。:older_man_tone1:

どうやって個人開発のモチベーションを維持したか

仕事の合間にやる個人開発は結構タフで、合間が空くとだんだん億劫になります。
モチベーション維持のために色々やってみました。

TODOを毎日見る

まず、Google Keepで思いついたアイディアをまとめたり、TODOを書くようにしました。
iphoneにTODOがあるので、思いついたときにパッと見ることができ、アプリを作るモードに自分を追い込むことができたと思います。

ライバルの存在

小さなお子さんがいる上司が、毎日プライベートでSwiftのコードを書いている事実を知り、お尻に火がつきました。:fire:
「このひとの前で"時間がない"など絶対言えないな」と思いました。
身近にライバルがいるのは結構、大事だと思います。

まとめてやらず、毎日少しずつやる

開発初期は週末にまとめて時間を作っていたのですが、途中から毎日数十分でもいいから少しずつ進めるようにしました。期間が空くとそれだけコードを思い出す時間もかかるし、気持ちを持ち上げないといけないので、毎日が個人的には良かったです。
GitHubに草を生やすのもモチベーションに繋がりました。

技術的な特徴

Realm

ローカルDBはRealmを使用しています。コードが平易で、サーバーサイド全然詳しくない自分でも書けました。
上記GIFにあるように、タブ切り替え時、瞬時に「あとで読む」に保存した記事が反映されていますが、ここもRealmの NotificationBlock を使っています。
NotificationBlockを使うと、Realmのオブジェクトが更新されたタイミングで通知を受け取ることができます。
さらに「更新」「追加」「削除」それぞれのイベントが取れるので、細かいUIのコントロールが可能です。

:point_down_tone2:あとで読むに保存したらTableViewを更新する処理

notificationToken = articles.addNotificationBlock { [weak self] (change: RealmCollectionChange) in
    guard let tableView = self?.tableView else { return }

    switch change {
    // クエリ初回発行時
    case .initial:
        tableView.reloadData()
    // データ更新時
    case .update(_, deletions: let deletions, insertions: let insertions, modifications: let modifications):                tableView.beginUpdates()
        // 追加
        tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
                             with: .automatic)
        // 削除
        tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
                             with: .automatic)
        // 更新
        tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
                             with: .automatic)
        tableView.endUpdates()
    case .error(let error):
        fatalError("\(error)")
        break
    }
}

RxSwift + MVVM

データバインディングにはRxSwiftを使用しました。
API通信も、レスポンスをObservableでラップしています。

// -- API通信の一部抜粋 --
// QiitaRequest型ならAPIコールできる。
// Observable化したAPIレスポンスを返す
func call<Request: QiitaRequest>(request: Request) -> Observable<Request.ResponseObject> {
    return Observable.create { [weak self] observer -> Disposable in
        guard let `self` = self else { return Disposables.create {} }
        let request = Alamofire.request(url, method: request.method, parameters: request.parameters, headers: nil)
            .responseJSON { response in

                switch response.result {
                // API通信成功したらnext, completedのイベントを発行
                case .success(let value):
                   if let json = value as? [Any] {
                        let responseObject = Request.ResponseObject(json: json, nextPage: nextPage)
                        observer.on(.next(responseObject))
                    }
                    observer.on(.completed)
                case .failure(let error):
                // 失敗したらConnectionErrorを通知する
                    let connectingError = ConnectionError(errorCode: error._code)
                    observer.on(.error(connectingError))
                }
            }
        request.resume()
        return Disposables.create {
            request.cancel()
        }
    }
}

API通信 → ViewModelでModelを加工 → Viewが切り替わるところまでRxで書いてみるのが、自分の中での裏テーマだったので、まだ全然勉強途中ですが何となく形にできて良かったです。

ちなみに開発初期は1モデルクラス用のAPIになっていました..(笑)
ちょうどいいタイミングでSwift実践入門が出て、API通信の実例が載っており、大変参考になりました。ほとんどのAPI処理を書き直しました..

個人開発をやってよかったこと

1からアプリ開発を体系的に体験できたこと

案件で1年以上、Swiftをやっていてそれなりに書けるかなと思っていましたが、1から開発するとなると、自分のスキルの狭さに気づかされました。
仕事となると担当を決められたスポットな開発になりがちですが、個人開発だと体系的に色々なノウハウを習得できるので、それは収穫でした。

フィードバック→改善のサイクルが楽しい

身近なひとに開発途中のアプリを見てもらいながら、改善を繰り返しました。
自分以外のひとの意見を取り入れることで、想像しえない方向にアプリが向かっていく体験が楽しいです。
決裁権が全て自分にあるのも良い。仕事で感じるしがらみがないので…笑

ポートフォリオになりえる

完成するとポートフォリオにもなりえます。自分はプログラミングスキルだけで勝負できないなと思ったのでライブラリの制作ではなく個人アプリ開発を選択しました。
勉強会とかで自己紹介するときもきっかけに使えるかと思います。

反省点

デザインを最初から作りこむ or アウトソーシングするべきだった

開発が終盤になると、あれもしたい・これもしたいと欲がでます。
終盤になってアプリに愛着が湧き、StoryBoard上でチクチクレイアウトをいじっていましたが、時間かかりすぎました。
最初からデザインは納得した形にしておくか、思い切って他のひとに依頼するか判断した方が懸命でした。

もっとAPIの仕様を調べておくべきだった

開発が楽しく、QiitaのAPIについて調査がおざなりになっていました。
開発終盤で、やりたいことがAPIではできないことを知り絶望の中、独自実装しました。

最後に

今後追加していく機能は下記を考えています。

  • タグ一覧の表示
  • 月間ランキング
  • 新着記事をスクロールしたらローディング

使ってみてもしよければ、Twitter(@hirothings)等でメンションいただけると大変嬉しいです!

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
150
Help us understand the problem. What are the problem?