小説家になろうのAndroidアプリ「なろうTime」を作った

小説家になろう(以下、なろう)*1のランキング閲覧と小説リーダのAndroidアプリ、なろうTimeを作った。

github.com play.google.com

まぁ作ったと言ってもリリースしたのが2016年5月なので結構前に作ってた。
ちょこちょこアップデートしてたんだが、忙しくなってり他にやりたいことあったりと自分の中の優先度が低くなっていて最近はアップデートしていなかった。
それで、最近落ち着いてきたのでアップデートしようと思ったが、そういえば作ったことをブログに書いていなかったので振り返りもかねてブログを書く。

作った経緯

なぜなのかは自分でもきちんとわかってないんだけど、なろうの小説を読むのが好きで結構読んでる。
ただ、自分はわりと完結済みの小説しかよまない。
また、あまり少ない文字数小説とかもよまない。
なのでその辺りのフィルターを簡単に切り替えて見れるようにしたかった。
というのが最大の理由。

あとは詳細検索があまり見やすくなかったのでその辺も改善したかった。

機能紹介

ランキング閲覧 & フィルター

なろうでは、日間、週間、月間、四半期、累計の計5種類のランキングがあって、それぞれ300件*2が最初の画面でみれるようになってる。
右下にFABを表示してあって、それをクリックするとランキングに対してフィルターをかけることができる。
このフィルターは完結済み、ジャンル、最大/最小文字数でフィルターをかけることができる。
これは経緯で語ったやりたいことをフィルターとして実装した。
ただアプリ使ってて思ったのが、フィルターはランキング結果に対してかけるため、フィルターかけた後は数が減ってしまう。
でも、詳細検索でリクエスト投げれば普通にフィルターかけた後の結果で300件とってこれるのでそうすればいいじゃんって思ってきたので実装しようと思ってる。

あと、公式のWebページだと最初ジャンル別に日間、週間…と表示されてるんだけど、1番最初に見たいのは総合ランキングじゃない?と思って総合ランキングをデフォルトで表示している。
f:id:Nshiba:20170620014639j:plain f:id:Nshiba:20170620014644j:plain

詳細検索

詳細検索をやりやすくしたかった。
でも使っててあんまり改善できてないな…って感じてしまってるのでいい感じにしていきたい。
f:id:Nshiba:20170620014811j:plain f:id:Nshiba:20170620014821j:plain

小説ダウンロード

小説なのでオフラインで読みたいことがまぁまぁある。地下鉄とか。
ただ、今ダウンロードの進捗をダイアログで表示しているのでダイアログ閉じてしまうとダウンロードが中断されてしまう。
これはほんとに良くない仕様なのでNotificationとかに変更したい。
なぜこんな仕様にしたんだろう…
f:id:Nshiba:20170620014833j:plain

しおり

小説なのでしおり機能あったほうがいいよね、ということで実装した。
公式ではユーザに関するAPIはないのとでローカルDBに保存してる。
あとたとえAPIあったとしても、オフラインで読む場合はローカルにないと読み込めないので。

小説リーダ

文字色と背景色が変えられる。
これはレビューで変えたい、という要望があった。
あと、暗い所で読んだりとか、人によっては読みやすい背景色とかがありそう。

また、自動しおり付与機能もある。
ブラウザで読んでいて結構しおり挟む前にタブ消しちゃった、とかあるので。
自分でしおりを気にしなくていいのは結構快適になる。
f:id:Nshiba:20170620014721j:plain f:id:Nshiba:20170620014724j:plain

技術的なお話

振り返ってみて、作ったときの俺はなんでこんな実装したんだろう、とか結構あるので、これから改善したいことも含めて書く。

設計

MVP
作った当初はまだぜんぜん設計とかわかってなくて最初はArchitecture考えずに実装してた。 でもいじってるうちにどんどん辛くなり、とりあえずActivity/Fragmentにこれ以上実装書きたくなくて、分離するためにMVPにリファクタした。
個人的には、個人アプリ + 中規模くらいのアプリだったらそこまでたくさん層はわけずにMVPでいいじゃないかな、と思ってる。
ただ、自分自身、今までたくさん開発経験があるわけではなく、これからって感じで、いろいろ試していきたいので、設計部分は今後も思考錯誤していきたい。

DB

Realm
たしか特に検討するわけでもなく、とりあえずRealmを触ってみたかったのでRealmにした。
でも実際は、ローカルDBの機能しかつかってないし、そこまで速さが求められる使い方でもないので、個人的に使いやすいと思ってるOrmaに乗り換えたいなぁなんて最近は思ってる。
特にRealmのマイグレーション辛い…(今年に入ってマイグレーションしてないけど改善されたのかな)
複数プラットフォームに対応する、なんていうことがあったらRealmの出番かもしれない。

非同期通信

Okhttp + RxJava
定番のやつ。
ただ、RxJavaは非同期通信の部分でしか使えてなくて、今後Kotlinに以降しようかな、と考えているのでそこでお役目御免かなぁと思ってる。
これなんでRetrofit使わなかったんだろ。

雑感

こんな感じですがランキング部分はそこそこ使いやすいかと思うのでぜひ使ってみてほしいです。
使ってみた感想や欲しい機能なんかがあったらレビューもらえるとすごい嬉しい。

また、いろいろ振り返ってみたら、リファクタしたい所やいれたい機能なんかが浮かんできた。
他にも普段つかってて、なんか使いにくい機能あったり、バグが残っていたりともどかしい部分があるのでやっていくぞ。
あと、Kotlinにするぞ。

*1:http://syosetu.com/

*2:ランキングAPIで返ってくる件数

2016年の振り返りと2017年の目標

正直振り返りと言っても何かやった記憶がほとんどない。完。

というのはさすがにあれなのでいくつか挙げてみる。

2016年振り返り

小説家になろう

唐突だが私は小説家になろうというサービスが好きだ。 なぜ好きなのか、というとまぁいわゆる厨二といわれるファンタジーやSF世界が好きだからである。 まぁそれで、アニメとか完結したラノベ、漫画でいろいろ想像したりするわけだが、それだけだと世界は限定される。 しかし小説家になろうでは、たくさんの人がたくさんの小説でその人だけの世界を展開しているわけである。 そういった新しい世界を探すのがとてもおもしろいのである。

そこで、自分のなんか関わりたいな、と思って作ったのが以下の奴。

github.com

github.com

しょうじきコードの品質はあまりよくない。半年前の俺はなんでこんなコードを書いていたんだ...ってな感じになってる。 また、これ書いてるうちになんか幾つか案が出てきたので2017年やっていきたい。

研究室のMTG

自分が所属している研究室は、自分で4期生でありまだまだできたばかりと思っている。
その為、研究室としての研究の進めかただったり、全体の進捗管理MTGのやり方など、ぜんぜん整っていない。
自分の研究だけやってれば?と思う気もしなくはないが、人間自分のやる気というのは周りの環境にとても左右されると思っている。
その為、研究室全体で研究をやる。という環境(空気と言ったほうがいいかも)が整っていたほうがやる気も出るし、進捗もでるだろう。
また、やっぱり自分が所属している組織なので今後もできるだけ続いて欲しいし強くなっていって欲しい。

という思いがあって10月くらいから、「毎週5人がそれまでの進捗発表をやる」というのをはじめた。
個人的にこれが結構良かったと思っていて、誰が何やってるか把握できるし、今の所属人数だとだいたい1ヶ月で1度程度の頻度で発表が回ってくるため、 かならず1ヶ月になんかしらの進捗は出さなければいけないのでやらない人もやる。
また、定期的に自分の研究をまとめることによって、今自分は何をやっているか、今突き当たっている問題は何か、今後は何をやらなければいけないか、が明確になる。
という、今までなんでやってこなかったって感じになってるので今後も続けていきたい。

じゃあなんで10月からなの?というのは2015年にもちゃんとMTGやっていったほうがいいよね、と思っていろいろやったんだが大失敗していろいろな人に迷惑をかけた。また、大失敗して自分のやる気がでなかった。ごめんなさい。

今年、協力していただいた人、ほんとにありがとう。 2017年も年明けからやっていきたいと思っているのでよろしくお願いします。

100万円貯金

3年くらい継続して目標にしてるけど無理だった。来年の俺よろしく。

2017年の目標

エンジニアとしての目標

アウトプットを増やす

細かい目標は結構あるが、総括するとこれに限る。 個人的には年を追うごとにインプットは増えてると思う。
じゃあそれを外に出してきたか?と言われると怪しいだろう。かろうじて小説家になろう関係で作ったものぐらいな気がする。
あとはQiitaとかに少し記事を書いた。

しかしそれだけだ。
なのでアウトプットをしていきたい。
細かいことを言うと

  • 技術系同人誌を買いて売る
  • 勉強会やカンファレンスで発表する
  • もっと細かいものでも作ったものを形にしてGithubに上げる
  • OSSにコミットする
  • 技術系の記事(ブログとかQiita)を書く

などを考えている。 あと、ついでにGithubで50スターくらい欲しい。頑張る。

時間を操る

もっと時間が欲しい、と思ったことありませんか?自分はあります。
なので2017年は時間を操れるようになりたいと思います。
一日30時間位欲しい

100万円貯金

2017年の俺頑張って。

まとめ

2017年は今まで貯めてきた経験値を使って進化する年にするぞ。

CPSLab VR Tourを作った

www.adventar.org

4日目です。 3日目は 岩井研残留日誌 - あつおの日常~あつおと過ごした365日~ でした。

自分の研究は複数の360度画像・動画を扱うのですが、その過程で所属している研究室のVR Tourなるものを作りました。

CPSLabVRTour
f:id:Nshiba:20161204232536p:plain
※ 画像が結構重いため初回ロード時は時間がかかります。

今回これの作成には three.js - Javascript 3D library を使用しました。

白いアイコンをクリックするとそこの情報が、矢印っぽいアイコンをクリックするとその方向の別の視点の画像に遷移します。
CPSLab入りたいなーとか、今年入ってきたB3生でどこに何があるのかまだわかってなかったときに参考にしてもらえると嬉しいです。

やっていること

やっていることは簡単でthree.jsにあるSphereGeometryを使ってMeshを作成しその上に各オブジェクトを配置しています。
オブジェクトの配置は、3D空間上にある球体の面に配置するためx, y, z座標が必要になります。
しかし現状、オブジェクトを配置するためのUIは作っていません。
そのため、手動で配置する場所の座標を打ち込んでいくしかないのですが、球面上に配置するオブジェクトのx, y, z座標なんて人間がすることじゃありません。 というかかなり厳しいと思います。まぁ他にももろもろ事情はあるんですが。

では、何でやってるかって言うと、仰角と方位角を用いて位置を指定し、配置する際にx, y, z座標に変換しています。
それならばどの方向でどれくらいの高さなのかを指定してあげるだけなので人間でもできますね。(仰角と方位角がわからない場合はググってください)

ちなみに仰角を phi , 方位角を theta , 球の半径を r にした場合、以下の式でx, y, z座標に変換できます。

  theta = theta * Math.PI / 180;
  phi = phi * Math.PI / 180;

  x = -r * Math.cos(phi) * Math.cos(theta);
  y = r * Math.sin(phi);
  z = r * Math.cos(phi) * Math.sin(theta);

まぁ3Dの分野では割と一般的に手法ではあると思います。三角関数は結構使われてると思うし。

最後に

もともと公開していたものですし、複数人の人には話して問題ないだろって感じだったんですが、プライベート空間でもあるので公開されるのが嫌な人がいましたらすぐに公開を停止します。その時は私まで連絡ください。

Okhttp + Gson を使ったJavaによるWebAPI入門

大学の授業の副手でJavaでWebAPIを使ったプログラムについてまとめることになったのでここにまとめます。

使用するライブラリ

JavaでHTTP通信だったりJSONを扱うのに一から自分で書くのって結構つらいですよね。
またエラーハンドリングとかしっかりやらないととても使えるものにはならないと思います。

よって今回はライブラリを使います。
※ 勉強するのにライブラリ使って良いのかよって思う人もいると思いますが、今回はサクッと使えるようなサンプル用意してくれっていうオーダーだったのでライブラリは使用します。

使用するWebAPI

Livedoor Weather Web Service
Livedoorが提供している天気予報APIです。 てきとうに選びました。まぁ認証は無しのAPIならなんでも良かったです。

HTTP通信

通信部分はOkhttpを使います。

HttpUrl

後述の Request インスタンスを作成するときにURLを指定するんですが、単純に文字列を渡してもいんですがUrlを作成するための HttpUrl クラスも存在します。
こちらはいろんなエンドポイント用に文字列を用意しておいたり、パラメータの指定を簡単に書けるようにしてくれます。
作成するには HttpUrl.Builder クラスが存在するので以下のように作成していきます。

HttpUrl.Builder builder = HttpUrl.Builder()
        .scheme(SCHEME)
        .host(HOST)
        .addPathSegment(FORECAST)
        .addPathSegment(WEB_SERVICE)
        .addPathSegment(JSON)
        .addPathSegment(ROOT);

上記で作成したURLは SCHEME://HOST/FORECAST/WEB_SERVICE/JSON/ROOT になります。

またパラメータを指定するのも簡単で以下のように書きます。

builder.addQueryParameter("city", "400040");

これによって今回使用するURLが作成できました。
SCHEME://HOST/FORECAST/WEB_SERVICE/JSON/ROOT?city=400040

Request

通信するときは Request インスタンスを作り OkHttpClient#newCall(Request request) を使って Call インスタンスを作ります。
通信方法には同期通信と非同期通信があり、同期通信は Call#execute() を、非同期通信は Call#enqueue(Callback callback) を使います。

また、 Request を作るときにHTTPメソッドを指定して作成します。
今回はGETメソッドとPOSTメソッドについて説明します。

GET

Request.Builder にURL渡して Request.Builder#get() を叩くだけ。
まあ Request.Builder のコンストラクタの実装を見るとわかると思いますが、デフォルトはGETメソッドが指定されてるので get() メソッド叩かなくても良いかも。

Request request = new Request.Builder()
      .url(builder.build())
      .get()
      .build();

builder はHttpUrlの項目で作成したもの。

POST

POSTメソッドは以下のように使用します。
JSONオブジェクトをつけてPOSTの Request を作成してみます。

MediaType jsonMedia = MediaType.parse("application/json; charset=utf-8");
Request request = new Request.Builder()
    .url(buildStateUrl())
    .post(RequestBody.create(jsonMedia, "{\"key\": \"value\"}"))
    .build();

だいたいこんな感じです。他にもいろいろあるので気になる人は自分で調べてみてください。

通信

あと残りは仕上げです。
先程述べた通り通信には2種類あり、同期通信と非同期通信です。

また、通信するには Call オブジェクトが必要です。
これまでに作ってきたRequestから以下のように作成します。

OkHttpClient client = new OkHttpClient();
Call call = client.newCall(request);

同期通信

特に難しいことはありません。 Call#execute() を叩くだけです。

Response response = client.newCall(request).execute();

非同期通信

こちらもそんなに難しいことはありません。非同期通信なのでCallbackが必要になるだけです。

client.newCall(request).enqueue(new Callback() {
    @Override
    public void onFailure(Call call, IOException e) {
        // Error
    }

    @Override
    public void onResponse(Call call, Response response) {
        // Success
    }
});

どちらも通信が終わったあとに Response オブジェクトが返ってくるのでそこから結果を取得します。
今回使用するAPIではbody部分にJSONオブジェクトが返ってくるので以下の用に受け取ります。

String result = response.body().string();

これで通信部分は終了です。
次はWebAPIから取得したJSONオブジェクトGsonを使って実際に使えるようにします。

Gson

それでは取得できたJSONJavaオブジェクトに変換して使えるようにしましょう。
まずJavaオブジェクトに変換するにはそれに対応するクラスを定義しないといけません。
例として以下のようなJSONがあるとします。

{
  "id": 321,
  "name": "nshiba",
  "birth": {
    "country": "Japan",
    "day": "1990-01-01"
  },
  "friends": [
    123,
    234,
    345,
    456
  ]
}

これをJavaオブジェクトに変換しようと思ったら、全体用のクラスと birth 用のクラス、2つのクラスが必要です。

public class UserEntity {
    @SerializedName("id")
    private int id;

    @SerializedName("name")
    private String name;

    @SerializedName("birth")
    private BirthEntity birth;

    @SerializedName("friends")
    private List<Integer> friendList;

    public int getId() {
        return id;
    }

    public String getName() {
        return name;
    }

    public BirthEntity getBirth() {
        return birth;
    }

    public List<Integer> getFriendList() {
        return friendList;
    }
}
public class BirthEntity {
    @SerializedName("country")
    private String country;

    @SerializedName("day")
    private String day;

    public String getCountry() {
        return country;
    }

    public String getDay() {
        return day;
    }
}

これでクラスの宣言はできたので以下のようにGsonを使って変換します。

UserEntity userEntity = gson.fromJson(result, UserEntity.class);

まとめ

これで一通り入門できたのかもしれないです。
一応こんな感じで実装していけば問題ないかな、思う範囲でサクッと作ったのですが問題がでそうな部分があったら教えてもらえると幸いです。
質問などあればコメントでお願いします。

また、今回作成したサンプルは公開していますので、コードはそちらの方が参考にできると思います。

github.com

DroidKaigi2016に行ってきました

droidkaigi.github.io

staff,speaker,等運営のかた、2日間お疲れ様でした!

いけなかった人は発表者の方が資料は公開してくれているので感謝しながら閲覧しましょう。 Day1, Day2

実際の開発の仕方やテストの書き方、ライブラリーの使い方等、自分の知らない技術をたくさん知れたのでとても勉強になり楽しかったです!

基本的にスライドにかなりまとまっているので見るだけでだいたい内容わかるので気になる方はぜひスライドみましょう。 僕自身も見たいセッションが重なっていたりしてみれないものが結構ありました。
後日、ビデオも公開されるみたいなのでビデオ公開されたら詳しく確認しようかな〜と思います。

今まであまり勉強会などは参加してなかったですが、今回参加してみて自分のしらない技術をしれるし、純粋にたのしいので今後は積極的に参加していこうかな、と思えました。

あとランチ美味しかったです。 1日目 f:id:Nshiba:20160221191046j:plain 2日目 f:id:Nshiba:20160221191050j:plain

丼 その2

この記事はCPS Advent Calendar 17日目の記事です。

www.adventar.org

昨日は ingressを通して考えたこと - 丼 でした。

今日は先日紹介した丼のAndroid版プロトタイプができたので紹介します。

続きを読む

この記事はCPS Advent Calendar 12日目の記事です。

www.adventar.org

昨日の記事は「esa.io が高いのでラボで代替ツールを作り始めた」でした。

elzup.hatenablog.com

我が研究室では、最近ドキュメント管理ツールとしてesaというサービスを利用していた。

esa.io

チームを作りそのなかでやっていくのだが、1つのチームにつき一ヶ月アクティブユーザー1人、各500円の利用料がかかる。 しかし、はじめの2ヶ月間はフリートライアルだったので、とりあえず初めて見るか〜と軽い感じで導入したらとても使いやすかった。

しかし…しかし…12月1日でついにフリートライアルが終わってしまった。 このまま使い続けたいのだが、我が研究室は実は結構な人数が所属しており一月の利用料がだいぶ高くなってしまい泣く泣く断念へ…つらい…

そこで我々は諦めきれずに自分たちで代替サービスを作ることにした。その名は「丼」である。 Web版のプロトタイプはできており、自分の担当はAndroidアプリの作成だ。 f:id:Nshiba:20151213001725j:plain

すみません…間に合いませんでした…

〜つづく〜