23
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

言葉はまるで雪の結晶、溶けていって消えてしまう、Androidチャットアプリ

Last updated at Posted at 2022-12-02

クソアプリ Advent Calendar 2022 4日目の記事です。

Official髭男dismの「Subtitle」という曲が心にとても沁みます。
歌詞の一部を引用します。

言葉はまるで雪の結晶
君にプレゼントしたとして
時間が経ってしまえば大抵
記憶からこぼれ落ちて溶けていって
消えてしまう

うっかりしたことが、フジテレビ系ドラマ「silent」を見逃してしまったこと。なんでも「泣けるドラマ」だそうで話題ですね。このドラマの主題歌でもあります。

何度も聴いているうちに、思い付いたのがこんなAndroidアプリです。

animation (2)-min.gif

相手のメッセージが溶けて消えるチャットアプリです。相手の発言は、表示されるや否や、溶けて消えます。

開発環境

Windows10で、Android Studioはバージョン2021.2.1です。

会話する「相手」は、株式会社リクルートが提供するAI・機械学習ソリューション「A3RT」のプロダクトの一つ「Talk API」です。
https://a3rt.recruit.co.jp/product/talkAPI/

ライブラリー「converter-moshi」を利用しました。
このライブラリーには、JSONパーサー「Moshi」と、HTTPクライアント「Retrofit」を含んでいるので、以下の一行をbuild.gradleファイルに記述してsync nowすれば済みます。

build.gradle
implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'

ありがとうございます、Square様!

Talk API利用方法

メールアドレスを登録して、APIのキーを発行してもらいます。
そのAPIキーを使って仕様の通りにリクエストを送信すれば、日常会話で応答してくれます。
「最近めっきり寒くなってきましたね」に対して「好きですよね」というように、
時折、頓珍漢な返答をしてくれるので、クスッとなります。

メールアドレス1つでAPIキーは貰えますし、APIキーさえあればcurlコマンドでも会話を楽しめますので、簡単ですよ。
ありがとうございます、株式会社リクルート様!

参考にしたQiita記事

湯婆婆Androidアプリを作ってみましたという自分の記事を参考にしました。
字がふわ~っと消えるアニメーションを今回も適用したかったので。

この湯婆婆Androidアプリは、字(正確に言えば、Stringを1個ずつandroid.graphicsパッケージのCanvasPaintを駆使してBitmapオブジェクトに変換したもの)を、android.animationパッケージのAPIを使って「上へ向かって」ふわ~っと浮かび上がらせるアニメーションですが、今回の溶けて消えるチャットアプリは、向きが「右斜め下へ向かって」なので、X軸Y軸のパラメータをいじればいいだけですので、当記事では詳しい説明は割愛します(湯婆婆Androidアプリの記事を参照してください)。

プログラム

Activityクラス1個と、リスト表示するのでRecyclerViewを使うことにしたので、それのAdapterクラスとViewHolderクラスも作りました。

以下、コードをすべて掲載すると長くなってしまうので、ポイントだけ残して、割愛できるところは省略しています。

RetrofitのAPIインタフェース

TalkApi.java
public interface TalkApi {
    @FormUrlEncoded
    @POST("/talk/v1/smalltalk")
    Call<TalkApiResponse> postRequest(@Field("apikey") String apikey, @Field("query") String query);
}

Talk APIのリクエストパラメータの説明によると、POSTリクエストであること、「apikey」と「query」というパラメータが必要だとのことなので、このようなメソッド定義となりました。

JSONデータクラス

Talk APIが返却してくれる雑談応答は、例えば以下のようなJSONです。

Talk APIが返却してくれる雑談応答の例
{"status": 0, "message": "ok", "results": [{"perplexity": 4.643262344645122, "reply": "趣味を楽しんでいる人は素敵ですよね"}]}

なので、以下のようなデータクラスを作りました。

TalkApiResponse.java
public class TalkApiResponse {
    public int status;
    public String message;
    public List<Result> results;

    public class Result {
        public double perplexity;
        public String reply;
    }
}

getter/setterメソッドを作るのが面倒くさくてpublicまみれです。Moshiに甘え切って完全に委ねます。
しかも、欲しい情報はただ一つ、replyだけなんですけどね。

Activityクラスの概観

このアプリの挙動としては、自分のメッセージを入力して送信ボタンをクリックしたら、自分のメッセージを表示させたそばから、Talk APIにリクエストを送信し、レスポンスされたメッセージを表示します。そんなActivityです。

MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        EditText inputEditText = findViewById(R.id.inputEditText);
        Button sendButton = findViewById(R.id.sendButton);

        // RecyclerView関連
        RecyclerView recyclerView = findViewById(R.id.listView);
        ChatAdapter adapter = new ChatAdapter(this);
        recyclerView.setAdapter(adapter);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        // ライブラリー「Retrofit」と「Moshi」を使います
        TalkApi api = new Retrofit.Builder()
                .client(new OkHttpClient.Builder().build())
                .baseUrl("https://api.a3rt.recruit.co.jp")
                .addConverterFactory(MoshiConverterFactory.create())
                .build()
                .create(TalkApi.class);

        // ボタン押下
        sendButton.setOnClickListener(v -> {
            String inputStr = inputEditText.getText().toString();

            // 自分のメッセージを表示
            adapter.add(inputStr);

            // Talk APIへPOSTリクエストして、レスポンスを受信する
            Call<TalkApiResponse> talkApiResponseCall = api.postRequest("Talk APIからもらったAPIキー", inputStr);
            talkApiResponseCall.enqueue(new Callback<TalkApiResponse>() {
                @Override
                public void onResponse(Call<TalkApiResponse> call, Response<TalkApiResponse> response) {
                    String reply = response.body().results.get(0).reply;
                    // 相手のメッセージを表示
                    adapter.add(reply);
                }

                @Override
                public void onFailure(Call<TalkApiResponse> call, Throwable throwable) {
                    // エラー処理
                }
            });
        });
    }
}

Callbackインタフェースの匿名オブジェクトのonResponseメソッド内で、レスポンスのステータスで成功したか否かチェックを怠っていたり、onFailureメソッドも空っぽというように、いろいろとちょっとお行儀が悪い箇所が多いのですが、勘弁してください。

このActivityのレイアウトリソースファイルには<RecyclerView>を配置しておきます。

RecyclerView.Adapterクラスの概観

public class ChatAdapter extends RecyclerView.Adapter<ChatAdapter.FukidashiViewHolder> {
    private final Context context;
    private final List<String> msgList;

    public ChatAdapter(Context context) {
        this.context = context;
        this.msgList = new ArrayList<>();
    }

    @Override
    public int getItemCount() {
        return msgList.size();
    }

    @NonNull
    @Override
    public FukidashiViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        return new FukidashiViewHolder(
                LayoutInflater.from(parent.getContext()).inflate(R.layout.fukidashi, parent, false)
        );
    }

    @Override
    public void onBindViewHolder(@NonNull FukidashiViewHolder holder, int position) {
        if (自分のメッセージ) {
            // 自分の吹き出しに普通に表示
        } else {
            // 相手(Talk API)のメッセージ

            // 文字列を1字ずつバラにする
            String[] strings = new String[message.length()];
            for (int i = 0; i < message.length(); i++) {
                strings[i] = String.valueOf(message[1].charAt(i));
            }

            // 1字ずつをBitmapにして、ImageViewに貼り付けて、GridLayputに配置する
            ImageView[] imageViews = new ImageView[strings.length];
            for (int i = 0; i < strings.length; i++) {
                imageViews[i] = new ImageView(context);
                imageViews[i].setImageBitmap(convertTextToBitmap(strings[i]));
                holder.otherMsgArea.addView(imageViews[i]);
            }

            // 溶かす
            for (ImageView imageView : imageViews) {
                meltingAnimation(imageView);
            }
        }
    }

    // Activityから貰う新メッセージ
    public void add(String newMsg) {
        msgList.add(newMsg);
    }
}

一行分のレイアウトリソースファイルは「fukidashi.xml」と名付けました。そこに吹き出しを配置しています。吹き出しの画像は9-patch画像です。黒い吹き出しが自分のメッセージ用、緑の吹き出しが相手のメッセージ用です。

fukidashi_other.9.png fukidashi_me.9.png

コード中にある、以下のメソッドは、

  • convertTextToBitmapメソッドは、StringBitmapに変換して返すメソッドです。
  • meltingAnimationメソッドは、ImageViewを右斜め下にふわ~っと消していくアニメーションをするメソッドです。

これらのメソッドは湯婆婆Androidアプリを作ってみましたという自分の記事にあるのとほぼ同じなので、ここでは説明は割愛します。

  • 自分のメッセージであれば、普通に出す。
  • 相手(Talk API)のメッセージであれば、表示したそばから溶けるように消す。

そんな処理の振り分けのif構文を、RecyclerView.AdapteronBindViewHolderメソッドの中で処理しています。

それでは最後にお聴きください

湯婆婆Androidアプリと同様に、「表示させた文字を、消す」シリーズのアプリを作ってみました。
まるで役に立ちません。古今東西のアプリとかシステムとかソフトウェアで、表示させた文字がふわ~っと消えるだなんてものは、見たことがありません。

でも、こうして『消えていくもの』を、冒頭で紹介したOfficial髭男dismの「Subtitle」だけでなく、多くのアーティストたちが歌い上げています。

手を伸ばし抱き止めた激しい光の束
輝いて消えてった 未来のために

LiSAの「ほむら」です。鬼も滅しないといけないですし。

大切な人も恋も愛も
性も、どうしよう
いつまでたっても守りきれないよ
いつかは消えてしまう

あいみょんの「あした世界が終わるとしても」です。世界が終わるのなら、そりゃ消えますよね。

悲しみっていつかは 消えてしまうものなのかなぁ…
タメ息は少しだけ 白く残ってすぐ消えた

SMAPの「夜空ノムコウ」です。全ては思うほどうまくいかないようです。

砂まじりの茅ケ崎
人も波も消えて

サザンオールスターズの「勝手にシンドバッド」です。
砂や波、といえば、もう一曲。

愛はまるで砂の城ね
出来た瞬間とたん波がさらう

斉藤由貴の「砂の城」です。おっとこれは、城は消えても、光る砂が残るわ。光る砂が残るわ。

以上、記憶に消えない、心に残る名曲集でした。

23
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?