Radiotalk Tech Blog

Radiotalk株式会社のエンジニアが知見や取りくみについてを共有するテックブログです。

Radiotalk Tech Talk Vol.1レポート「テーマ『Radiotalk』」

f:id:radiotalk-tech:20210518121213p:plain 毎回1つのテーマを定めて行う、Radiotalk主催のテックイベント「Radiotalk Tech Talk」。記念すべき第1回目では、「Radiotalk」をテーマに、Radiotalkが描くビジョン、そして実際にRadiotalkのサービスの基幹を形成するインフラ設計について、Radiotalk社員による発表を行いました。

(1)Radiotalkが実現するビジョン(Radiotalk 代表取締役 井上佳央里 @JD_KAORI

最初のセッションは、Radiotalk株式会社 代表取締役の井上佳央里より、「Radiotalkが実現するビジョン」。Radiotalkがサービスとして目指すビジョンをお話しました。(以降、各章ではスピーカーたちの発言要旨をまとめます) f:id:radiotalk-tech:20210518005129p:plain

トークをエンタメに 〜音声の力で「個の熱狂経済圏」を作る

Radiotalkが掲げるビジョンは「トークをエンタメにする」。人生を送るうえで欠かされることのない「会話」がエンタメになったら毎日がエンタメの宝庫になるのでは、という思いからサービスを運営しています。

f:id:radiotalk-tech:20210518005222p:plain メルカリやBASEなどを使って個人が自由に商品を売る「個人経済圏」が注目されているいま、Radiotalkは音声の強みである「熱狂度の高さ」に注目し、「熱狂経済圏」を作り上げようとしています。この「熱狂経済圏」を作り上げるビジネスモデルとして、Radiotalkが取り入れているのが、「ギフティング」。ライブ配信や収録配信においてリスナーから贈られる有料ギフトを通じ、「ラジオトーカー」と呼ばれる配信者たちが自らのトークを収益化しています。

f:id:radiotalk-tech:20210518005244p:plain

個人のストーリーや思想にファンが生まれ、経済が生まれていく 音声配信の魅力は「見た目とか年齢とか全然関係なく人気になれる」という点。これまで動画系のライブ配信では本筋とはされてこなった層の人々にも、脚光が当たってきているのです。 f:id:radiotalk-tech:20210518005302p:plain

その人が語るストーリーや言葉、思想にリスナーがどんどん引きつけられてファンになり、それに対して課金が発生していく──。こうした「人間的なボキャブラリーやパーソナリティが共感を呼び、応援される経済圏」をRadiotalkで作っていきたいと考えています。

(2)「インフラ環境が変更される可能性ってなんだかんだないよね?」「あるよ。」APIにおけるインフラ層の抽象化と実例(Radiotalk CTO 斉藤裕気 @gamu1012

続いては、Radiotalk株式会社 CTOの斉藤裕気による「APIにおけるインフラ層の抽象化と実例」。Radiotalkの基盤を支えるサーバサイドの設計における「抽象化」の取り組みについてお話しました。

f:id:radiotalk-tech:20210518005335p:plain

f:id:radiotalk-tech:20210518005344p:plain

サービスが変化し続ける時代に「抽象化」の概念は重要

サービス開発におけるエンジニアリングは、「完成品をリリースをして終わり」という時代から、ユーザーの反応や市場の反応を見ながら改善を継続し、ソフトウェアを進化させ続けていく、いわば「リリースがスタート」の時代へと突入しました。

いまやサービスはつねに変化し、それに応じて機能や実現方法も変化することが大前提となっています。そんななか、実装したサービスやソフトウェアを変化、改善し続けていくために強力な役割を果たす要素のひとつとして、今回は「抽象化」にフォーカスしてお話します。 f:id:radiotalk-tech:20210518005414p:plain

相次ぐインフラの変更に対応するための「抽象化」

Radiotalkのようなサービスにおいては、機能の変化に応じてユースケースや、使用するインフラの構造もめまぐるしく変化していきます。今回はこのインフラ部分に焦点を当ててお話をしたいと思います。

f:id:radiotalk-tech:20210518005438p:plain

サービス変化の過程では、必要に応じて裏側のインフラもまた変化していくことを想定しなければなりません。データベースがMySQLからPostgreSQLになる場合もありえるでしょうし、キャッシュがファイルベースからRedisになることもありえるでしょう。ファイルストレージがS3から別のクラウドストレージになることも今後十分にありえます。前職時代こそ「インフラの変更は本当に発生するのか?」と思いながら取り組んでいた抽象化ですが、Radiotalkに入社してからは、よりその必要性を実感するようになりました。

f:id:radiotalk-tech:20210518005454p:plain

Azure→OpenStack→CGP... 1年半で2度も基盤インフラを変えたRadiotalk

ここで、サービスとしてのRadiotalkがインフラをどのように変化させていったか、振り返ってみたいと思います。

Radiotalkはエキサイト株式会社の社内ベンチャーとしてスタートし、2017年8月にサービスを開始しました。当初はオフショアで開発を行っていたため、インフラとして先方の開発チームからも利用しやすいMicrosoft Azureを使っていましたが、その後社内開発チームの立ち上がりにあわせ、当時エキサイトがメインで利用していたOpenStackへと移行しました。

f:id:radiotalk-tech:20210518005515p:plain

f:id:radiotalk-tech:20210518005521p:plain

その後、エキサイトではプライベートクラウドからパブリッククラウドへの移行が検討されていたのですが、RadiotalkとしてはSpeech-To-Textなどをいち早く利用したいという思いから、いちはやくパブリッククラウドへと舵を切り、2019年2月にGCPへとインフラを移行しました。

わずか1年半のあいだに2回も基盤インフラを変えるというのは、さすがに異例のことでした。しかし、どう成長していくかわからないスタートアップという舞台においては、必要なところを抜き出しながらインフラを変化させていくというアプローチが非常に有効だったのです。

f:id:radiotalk-tech:20210518005534p:plain

「抽象化ありき」で考えてもいけない

f:id:radiotalk-tech:20210518005600p:plain

抽象化にあたって大事なことは要求にフォーカスすることです。今回の要求は「データの永続化」です。ここで間違えてはいけないポイントは、たんに「MySQLを抽象化する」のではなく、「データの永続化のためにデータストアを抽象化させ、MySQLで実現する」ということです。

f:id:radiotalk-tech:20210518005615p:plain

次に大事にしたのが「YAGNI(You Ain't Gonna Need It:実際に必要になるまで機能を追加しない)」という考え方です。具体的に存在する要求や課題をきっちり抽象化するのは大事であると同時に、それが難しければ抽象化をしないという方法もあります。「なんでもかんでもとにかく抽象化すればよい」という考えになってしまうと、Redisのようにもともと多様な使い方が出来る仕組みの前では、とくに痛い目を見ることになってしまいます。

もっとも、こうした考え方はあくまで原則論なので、そのまま現場に持ってこれるかというと、難しいでしょう。エンジニアやプロダクトマネージャーらと議論を重ね、「本当にユーザーが求めているものはどういうことか」というところにきちんと向き合わなければならないのは言うまでもありません。

f:id:radiotalk-tech:20210518005631p:plain

(3)Radiotalk Androidアプリにおけるモジュール分割の課題とこれから(Radiotalk Androidエンジニア 牧山瞭 @_rmakiyama

f:id:radiotalk-tech:20210518005643p:plain

続いてのLTは、Radiotalk Androidエンジニア・牧山瞭による「Radiotalk Androidアプリにおけるモジュール分割の課題とこれから」。RadiotalkのAndroid向けアプリの開発現場で実践しているモジュール分割の方法についてお話しました。

RadiotalkのAndorid版アプリは2018年10月にリリースしましたが、当時の設計からマルチモジュールを採用していたので、今回は「ひとつのAppモジュールをどう分割するか」といった話は出てきません。

また、開発体制についても、現在は私ひとりがフルタイムで行っており、これから複数人体制での開発へと取り組んでいこうとしている状況です。

サービスのフェーズや開発組織の大きさによっても事情は異なってくるので、すべてのプロジェクトでこれからお話する方法がオススメ、というわけではないことは、あらかじめ念頭においておいていただければと思います。

f:id:radiotalk-tech:20210518005706p:plain

現在のRadiotalk Android版のモジュール構成

現在のRadiotalk Android版アプリにおけるモジュール構成は、図の通りとなっています。

f:id:radiotalk-tech:20210518005731p:plain

「features」と表している部分は、検索やライブ配信など、機能(feature)単位で分割したモジュールがいくつもある形となっています。いわゆる垂直方向の分割です。

「clients」と表している部分は、TalkClientや、LiveClientなど、Radiotalk内でメインとなるロジックをまとめたモジュールがいくつかある形です。これによって、開発の当初から大きく変わらない主要な処理を共通化し、実装の高速化を図っています。

f:id:radiotalk-tech:20210518005744p:plain

「screen_navigation」モジュールでは画面遷移を抽象化させています。マルチモジュール環境では画面遷移で問題が起きやすい部分があるのですが、ここでは循環依存を回避しつつ、機能間での画面遷移を実現させています。

f:id:radiotalk-tech:20210518005758p:plain

さらにドメインモデル的なアプローチとして、収録やライブ配信といった領域ごとにそれぞれモジュールを切り出し、ライブラリ的な扱いができるように実装しています。これらのモジュールを使う際は基本的にインターフェイス経由で処理を呼び出すようにしており、使う側からは具体的な技術詳細を隠すよう実装しています。すなわち、機能を呼び出す側は、その裏側の仕組みを一切意識する必要がありません。

f:id:radiotalk-tech:20210518005811p:plain

機能ごとに独立して開発し、主要ロジックも共通化しているため、個々の部分で改善点はあるにせよ、全体としては特にストレスなく実装が行えています。

もっとも、これは現在私がひとりで開発を行っているという前提によるものなので、今後のプロダクトの成長や、開発チームの拡張も視野に入れていかなければいけません。

f:id:radiotalk-tech:20210518005827p:plain

現状の課題は「制約設計」「機能ごとのインフラの取り回し」「設計思想の明文化」

先ほどCTOの斉藤のLTにもあったとおり、いまやプロダクトを作って終わりの時代ではありません。機能追加や開発組織の変化も含め、プロダクトの成長に合わせて常に改善を続ける必要があります。これを踏まえ、現在の課題について考えてみたいと思います。

f:id:radiotalk-tech:20210518005852p:plain

Featureモジュールの中身はよくあるMVVMパターンを用いており、1モジュールの中でパッケージごとにレイヤー分けしています。

f:id:radiotalk-tech:20210518005907p:plain

AndroidではKotlinで開発しているのですが、この言語の制約を駆使しても、プレゼンテーション層からインフラ層を呼び出せてしまうなど、制約の設計が弱いかなと感じています。またFeatureモジュールごとにインフラ層を持っているので、別のFeatureで定義しているAPIを使いたい、メモリキャッシュにアクセスしたいというときなど、インフラの取り回しが効きづらい部分も出てきています。

f:id:radiotalk-tech:20210518005921p:plain

また、Feature間のモジュール依存についてもあまり整理できておらず、複数人のチームで開発する上ではあまり統一した設計思想が作れていないという点が目下の課題となっています。ひとりでだけで開発している状態では自身が気をつければよいですが、チームが大きくなるとコードレビューで設計の崩壊を防ぐのことはどうしても困難です。

そんななか私が注目したのは、最近読んだ『Design It! プログラマーのためのアーキテクティング入門』(Michael Keeling著/オライリー・ジャパン)にあった次の一文です。

f:id:radiotalk-tech:20210518010008p:plain

改善にあたり、設計によって「何を」促進させたいのかを明確にし、そしてその設計思想が見えるよう意識することで、チームでの開発体験を向上させたいと考えました。

今後の改善策:「モジュール間依存の強制」「開発メンバーの判断コストを下げるモジュール設計」「巨大なCommonモジュールの分割」

これらの話を踏まえ、これからのモジュール構成について現時点で考えていることをお話ししたいと思います。

まず、これからの開発チームが達成したいことについて考えてみました。

これらの話を踏まえ、これからのモジュール構成について現時点で考えていることをお話ししたいと思います。

まず、これからの開発チームが達成したいことについて考えてみました。

f:id:radiotalk-tech:20210518010048p:plain

これを踏まえ、中期的には、次の図のようなモジュール分割に変えようと考えています。

具体的には、Featureモジュールとして1モジュールに実装されていたレイヤー表現をモジュールとして分割すること、ひとつの大きいCommonモジュールとして共通化した処理を持っていた部分を分割したいというところ。また細かい部分では、次の改善への準備を目指す作り方を進めていきたいと思っています。

f:id:radiotalk-tech:20210518010102p:plain

それぞれについて、もう少しだけ説明していきます。

まず、今までパッケージレベルで分けていたレイヤーをモジュール分割します。これにより、依存していないモジュールは参照できないため、たとえばプレゼンテーション層からインフラ層を呼び出せないといった制約を強めることができます。また、Kotlinの場合は、internal修飾子を使うことによって依存していないモジュール内でのみクラスを呼べるよう制約も設けられるため、インタフェースのみを公開し技術的関心事を隠蔽することができるようになります。これにより、ユースケースには「何をするのか」ということを記述し、インフラ層には「どうやって実現するのか」によりフォーカスして実装を進められるメリットもあります。

また今の段階では、ユースケースとインフラを担うモジュールをコンテキストごとに切らず、1モジュールにまとめることで、開発時の判断コストを下げることも考えています。ここには、これから新たな開発メンバーが入ってくることを考えたときに、コンテキストを簡単に理解してもらうことは難しいためであることと、変化の激しいスタートアップだとそのコンテキスト自体も今後変わっていくことが十分に考えられるためという意図があります。

f:id:radiotalk-tech:20210518010119p:plain

また、これまではCommonモジュールとして「便利モジュール」を設けていたのですが、すべてのモジュールから依存されているがゆえ、このモジュールのクラスを変更すると、不必要な個所までまとめて再ビルドが起きるという問題を抱えていました。そのため、こちらは関心の分離をするため、レイヤーごとに分割していきます。

f:id:radiotalk-tech:20210518010134p:plain

長期的にはしっかりのドメイン層を設け、ドメインを中心としたアーキテクチャに寄せていきたいなという思いがあります。まずは依存の逆転を見据え、インターフェースをしっかり切っていく実装をしていきたいと考えています。

f:id:radiotalk-tech:20210518010152p:plain

成長し続けるプロダクトのため、設計も改善を続けていく

「ソフトウェアは作って終わりではない」というのが、Radiotalkの開発組織全体としての哲学です。プロダクトや開発組織の成長とともに、常に設計も改善を続けていくという気持ちを大事にしています。また、設計判断にはバズワードに飛びつくだけではなく、その導入に意味をもたせることを意識しています。

このような話を一緒にワイワイしながら成長させてくれるメンバーも募集しています! >>音声エンターテインメントを創るVoiceTechベンチャーのテックリード
>>急成長中VoiceTechベンチャー"Radiotalk"のエンジニア募集【サーバサイドエンジニア】

f:id:radiotalk-tech:20210518010357p:plain

(4)Radiotalk iOSの音声配信技術について(Radiotalk iOSエンジニア 竹内彰吾)

f:id:radiotalk-tech:20210518010413p:plain

最後のLTは、Radiotalk iOSエンジニア 竹内彰吾による「Radiotalk iOSの音声配信技術について」。Radiotalkの根幹をなす音声配信技術についてお話しました。

f:id:radiotalk-tech:20210518010429p:plain

Radiotalkの「再生まわり」

iOSアプリにおけるオーディオの再生・録音・ミックス・生成まわりをサポートするのが「Core Audio」。以下のように複数のフレームワークで構成されています。

f:id:radiotalk-tech:20210518010529p:plain f:id:radiotalk-tech:20210518010536p:plain Radiotalkでもっとも多く使っているのが、「AVFoundation」と呼ばれるフレームワークです。ストリーミング再生の場合、サーバーから音声のURLを取得し、AVFoundationに含まれる「AVPlayer」モジュールに渡すことで機能を実現しています。

f:id:radiotalk-tech:20210518010555p:plain ダウンロード再生、すなわちiPhoneの端末上にある音声ファイルを再生する場合には、同じくAVFoundationの中にある機能で「AVAudioPlayer」モジュールを使っています。この仕組みではストリーミング再生はできませんが、ローカルファイルの再生に適しているため、採用しています。

iPhoneのコントロールセンターやロック画面で、現在再生しているメディアの情報が表示されるのをよく目にしますが、こうした挙動もその都度実装しなければいけません。Radiotalkでは「MediaPlayer」クラスをimportし、その中の「MPNowPlayingInfoCenter」「MPRemoteCommandCenter」モジュールを組み合わせて実現しています。

f:id:radiotalk-tech:20210518010621p:plain

「MPNowPlayingInfoCenter」にはメディアの再生時間や再生中のタイトル、配信ユーザー名を渡しています。コントロールセンターで再生ボタンや一時停止ボタン押されたときには「MPRemoteCommandCenter」でイベントをキャッチし、アプリの挙動と連携するようにしています。

Radiotalkの「録音まわり」(単体収録の場合)

続いては、音声の録音まわりを説明します。

ユーザーが1人でトークを録音し、配信する場合には、先程と同じく「AVFoundation」をimportし、「AVAudioRecorder」モジュールを使用しています。

f:id:radiotalk-tech:20210518010642p:plain

「AVAudioRecorder」にまずローカルの音声データを渡し、さらに録音の形式を設定ファイルで渡しています。その後AVAudioRecorderのrecordメソッドを叩くと、録音がスタートします。

Radiotalkの「録音まわり」(リモート収録の場合)

Radiotalkには、リモート上でゲストを呼んで一緒にトークをし、それを一つの音声データにまとめて配信する機能があります。この機能は「Agora」という配信プラットフォームを利用しています。

f:id:radiotalk-tech:20210518010659p:plain

この「Agora」は、複数の音声インプットをミックスし、一つの音声データとして出力してくれます。具体的な使い方としてはローカルに音声ファイルのパスを作成し、AgoraSDKに渡すことで、Agora上でトークを録音し、ローカル上に音声ファイルを完成させるというものです。Radiotalk側ではとくに実装はしておらず、Agoraのサービスを純粋に利用しているという形です。

Radiotalkの「音声編集まわり」

Radiotalkでは、収録した音声をあとから編集・エフェクトをほどこしたり、スピードや音量を変えることができます。この機能はAVFoundationをimportし、その中の「AVAudioEngine」を使用して実現しています。

f:id:radiotalk-tech:20210518010738p:plain 「AVAudioEngine」は音声の生成、処理、入出力をトータルに扱うモジュールです。使い方としては、このAVAudioEngine に、音量を変更する「AVAudioPlayerNode」、音の高さや速度を変更する「AVAudioUnitTimePitch」を装着することでエフェクト機能を実装しています。

続いて、波形編集の部分です。

Radiotalkでは収録した音声ファイルの一部分を切り抜いたりできる編集機能を提供していますが、この実装もAVFoundationを使用しています。具体的には「AVAudioRecorder」で波形を取得し、編集した後の波形を用いて「AVConposition」で音声ファイルを再生成しています。

f:id:radiotalk-tech:20210518010755p:plain

Radiotalkの「ライブ配信まわり」(単独ライブ配信)

Radiotalkにはライブ配信の機能が備わっており、配信者が配信をしている音声を大勢の人がリスナーとして聞くことができます。このライブ配信のアーキテクチャについて説明していきます。

f:id:radiotalk-tech:20210518011000p:plain

まずは「単独ライブ配信」。これは、1人のホストと複数のリスナーの場合で構成される場合を指します。配信者がiPhoneに対して喋っている音声データを取得し、それを音声サーバーのRTMP(Real Time Messaging Protocol:リアルタイム通信プロトコル)として配信することで、リスナーがそれを生配信として聞くことができます。

加えてRadiotalk側のサーバーでは、リスナーからギフトやコメントが送信された際にAPIリクエストを行います。リスナー側は、これをWebSocket(双方向通信プロトコル)経由で受け取ることで、自分を含めリスナーからのギフトやコメントがリアルタイムに表示される仕組みとなっています。

Radiotalkの「ライブ配信まわり」(コラボライブ配信)

もうひとつが「コラボライブ配信」。これは、配信者がリスナーの中から何人かをピックアップし、一緒に配信できる機能です。ピックアップしたリスナーのことを「ゲスト」と呼んでいます。

配信者とゲストとの会話には、先程のリモート配信でも出てきた「Agora」を使用しています。

配信者側のアプリでは、まずAgora上にルームを作成し、ゲストに対してRadiotalkのサーバー経由で招待を送ります。ゲストはWebSocketでこれを受け取ってAgoraのルームに参加し、複数人が同時に会話できる状況ができあがります。さらに、このAgoraからミックスされた音声をRadiotalkのサーバーで取得し、リスナーにHLSで送信するという仕組みになっています。

Apple製品間の「サンプリングレート違い」を埋めるための工夫

iPhoneから音声データを取得し、音声サーバーに流すまでのあいだに、低レベル(ハードウェア制御に近い)APIを用いた実装が必要になります。実はこの部分こそが、結構頭を悩ませるポイントでした。iPhoneやiPad、AirPodsなどのApple製品は、それぞれ製品ごとに機器の音声サンプリングレートが異なっています。Radiotalkでは、これらのサンプリングレートをひとつに統一するため、アプリ側で変換を行っています。

f:id:radiotalk-tech:20210518011058p:plain f:id:radiotalk-tech:20210518011107p:plain まず、サンプリングレートを取得する方法ですが、AVFoundationをimportし、「AVAudioEngine」と「AVAudioNode」を組み合わせて実装しています。具体的にはマイクから受け取る音声と、あらかじめ設定したサンプリングレートに基づくアウトプット定義を比較し、44.1KHzで入力されたものはそのまま変換しないで通しています。

f:id:radiotalk-tech:20210518011122p:plain 一方、16KHzで入力された場合、過去にうまくいかなかった事情から、いちど48KHzに変換したうえで44.1KHzに再度変換する、というトリッキーな操作をしています。アップサンプリング(サンプリング周波数を上げる)操作はよい感じに進むのですが、48KHzを44.1KHzに下げるといったダウンサンプリング(サンプリング周波数を下げる)はなかなか難しく、(バッドノウハウ的に)このような処理を行っているのが現状です。

最後に「音声開発あるある」

音声周りの開発をしているうえでの「あるある」なのですが、コワーキングスペースなど、周りに人がいる中で開発をしていてハウリングを起こしてしまったり、音楽を聞きながら作業をしていて、ホストとゲスト側それぞれの疎通を確認するために端末のイヤホンを差し替えていたら、その拍子に爆音の音楽が流れてしまったり…… ということがよくあります。

あとは、Android側をホストにして、iOSをクライアント側にしていた際、環境が異なるAndroid側から音声が送られたのを聞いて、うっかりiOS側の問題が解決したと思い込んでしまったり……。このように恥ずかしい気持ちになることも多いですが、概ね楽しく開発を行っています。
>>音声エンターテインメントを創るVoiceTechベンチャーのテックリード
>>前年比5倍!急成長中の音声配信サービスiOSエンジニア募集!
>>音声配信プラットフォーム"Radiotalk"のAndroidエンジニア!

Radiotalk株式会社のCTOに就任しました

こんにちは、サイトウ です。

この度、Radiotalkの取締役CTOに就任しました。

就任にあたり、この記事では現在の具体的なRadiotalkの開発組織と次に目指す開発組織についてご紹介します。

こちらのインタビューもあわせて見ていただけると、幸いです!

www.wantedly.com

www.wantedly.com

Radiotalkの開発組織

2021/05/13現在の開発メンバー

PM
1名

デザイナー
1名

サーバーサイド
4名

iOS
3名

Android
2名

QA
1名

他にも副業やインターンのメンバーがおり、チームでRadiotalkを開発しています。

自分を含む社員のメンバーは全員Radiotalkで配信をしていたり、

今まで100人近くのユーザーと会ってきたり、

サービスに対する理解が深いメンバーが多いのが一番の特徴です。

また社員全員がテックカンファレンスや勉強会などで登壇しており、アウトプットを通じて、社内や技術コミュニティに対して活動を行っているのも大きな特徴です。

なにを大事にしているのか

下記の2つを開発組織として大事にしています。

  • エンジニアリングを通して、事業拡大に貢献しつづけること
  • すばやく価値を提供し、仮説検証を行えるようにすること

"つづける" という点がポイントで、瞬間的な解決ではなく、線で捉えて、開発の意思決定をしています。

音声配信サービスはまだ勝ちパターンが定まっていない市場であり、試して初めてわかることがとても多いです。

そのために悩むより、スコープを絞ってでも、素早くユーザーに届け、反応をもらいというのを開発組織として大事にしています。

これからの開発組織とCTOとしてやっていくこと

上記2点を強化するために、CTOとしてこれらのことをやっていきます。

  1. エンジニアリングを通じて、事業貢献を続ける
  2. 事業貢献を続ける開発組織をつくる
  3. 1と2を支える開発文化をつくる

技術と事業の両輪でエンジニアリングすることを楽しいと思えるような組織にしていきます。

具体的な課題

事業貢献を続ける開発を目指すとはいえ、具体的な開発課題もあります。

各領域であげると、

Android

  • 音声編集機能の実現
  • テロップ動画機能の実現

iOS

  • 音声編集、テロップ動画の改善
  • Swift UIを見越したアーキテクチャの改善

サーバーサイド

  • より安定したライブ、リアルタイム対話の実現
  • 音声コンテンツのレコメンドの改善
  • 音声コンテンツの検索の改善
  • WEBでのログイン、収録やライブ配信の実現

もちろん他にまだまだあり、やりたいこと、解決したいことはたくさんあります!

ぜひ詳細はカジュアル面談等でお話しましょう!

最後に

もっと多くの人が気軽に音声配信をはじめらるように、

もっと多くの人が満足のいくトークができるように、

配信者とリスナーとのコミュニティがどんどん生まれていくように、

トークを新しいエンタメにできるようにこれからも精進していきます!


トークや音声配信、ライブ配信が面白そうと感じている方

ユーザーとの距離がものすごい近いサービスをつくりたい方

エンジニアリングが大好きな方

一緒にRadiotalkを開発しませんか!?開発メンバーを積極的に募集しています!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

カジュアル面談ご希望の方は上記Wantedly URLの「話を聞きに行きたい」を押して、「まずは話を聞いてみたい」とご連絡ください!

RadiotalkのAndroid/iOSのサポートバージョンの基準について

こんにちは、サイトウ です。

今回はRadiotalkのAndroid/iOSのサポートバージョンの基準についてご紹介します。

なぜ基準を定めたのか、その基準を使い、どういう心構えで判断するのかなどをまとめました。

なぜ基準を設けるのか

開発の効率化のためです。

以前は、なにかしらの理由によりサポートバージョンを引き上げないといけなくなってからあわてて、議論、判断、告知、実際の引き上げ作業をやっていたため、議論や判断もメンバーそれぞれになってしまい、無駄が多くなってしまっていました。

そこで基準を設けることで、それをベースに議論、判断が行えるので、チームでのコミュニケーション効率があがり、対応もスムーズになるだろうという狙いです。

Radiotalkにおけるそもそもの心構え

Radiotalkは積極的にサポートバージョンを引き上げたいというわけではありません。

我々は下記のような心構えで運用しております(※社内向けドキュメントの引用)

基準を設けたからといって、機会的に引き上げるということはせず、毎回チーム内で合意をとって、行います。

この基準に達したから、すぐにサポートを打ち切るというわけではありません。

広くサポートし、多くのユーザーに楽しんでもらうことを念頭においてください。

サポートを切ることでユーザーに不便を強いる面と下記のようなメリットを踏まえて、総合的に判断してください。

- OSレベルの新機能を利用することでサービスの成長につながる
- 下位のOSの対応をやめることで開発効率が向上する
- 下位のOSのバグやセキュリティリスクを回避できる

Android/iOSのサポートバージョンの基準について

直近30日のアクティブユーザーのうち、対象OSのユーザーが3%未満である

これを大前提の判断基準とし、上記のようなメリットを踏まえ、総合的に判断します。

また打ち切ることを決めたら、2ヶ月以上のバッファを持って、ユーザーに告知することも1つの推奨事項としました。

このときにアプリが使えなくなるのか、最新バージョンへのアップデートができなくなるのか、動作保証はどうなるのかなどをできる限り具体的に伝えるようにしたいと思います。


各社で公開されている基準を参考にし、草案を作り、各アプリのエンジニア、プロダクト開発メンバーと協議し、決定しました。

参考にさせていただいた各社様

実はまだ作成したばかりでこの基準を使ってのサポートバージョンを引き上げてはおりません。

運用していくなかでアクティブな比率や告知の期日などは改善していきます!

もし読んでいただいている皆さまの企業で、サポート基準がありましたら、ぜひ教えて下さい!

最後に

Radiotalkではサービスとユーザーのことを中心に考え、開発効率とのバランスをとりながら、エンジニアリングしてくれるメンバーを募集しています!

www.wantedly.com

www.wantedly.com

2020年のRadiotalk Androidアプリの開発現場

こんにちは、Androidアプリエンジニアの牧山(@_rmakiyama)です!

RadiotalkのAndroidアプリは、2018年のリリースから約2年が経ちました。この間に、Kotlin Coroutinesが正式版になったりJetpackがどんどん充実してきたりと、Android開発の事情も大きく変化していきました。それに合わせてRadiotalkのAndroidアプリ(以下Radiotalkアプリ)も少しずつ変化をしていっています。今回はそんなRadiotalkアプリの2020年時点での開発現場についてまとめます。
スタートアップ企業でどのような開発が行われているかのイメージが少しでも持っていただけたら嬉しいです。

アーキテクチャ

Radiotalkアプリではマルチモジュールを採用しています。 機能ごとでのモジュール分割を採用しています。別途特別なモジュールとして、Radiotalkのトークや番組など、主要なドメイン操作に関する機能をクライアントとして切り出し、処理を共通化させています。

f:id:radiotalk-tech:20201222224246p:plain

それぞれのモジュールは、MVVM + レイヤードアーキテクチャを採用して実装しています。

f:id:radiotalk-tech:20201222224418p:plain

少し変わっているところでいうと、UseCaseと名前のついているクラスがアプリケーションロジックとリポジトリパターンの責務を持つ実装にしている部分です。
ひとつのUseCaseにはひとつのパブリックメソッドを定義し、ユースケースに合わせてローカルとリモートのデータのやり取りをし、必要に応じてViewModelが求めるデータを返却します。

詳細はまた別の機会にということで割愛しますが、開発当初はRxJavaを全面採用していたところをKotlin CoroutineやLiveData、最近だとFlowを採用したりと、会社としてのフェーズとAndroid開発環境の進化とともにアーキテクチャも少しずつ変えていっています。まだまだチームも小さいので、開発者が責任を持ちながらも楽しく開発できることを第一に考え、積極的に新しい技術も取り入れています。

これからは、チームが大きくなったときのことを想定し、さらにスピードアップできるようなりアーキテクチャも考えています。

開発体制

Androidエンジニアはリリース当初からフルタイムだと私のみで、業務委託だとiOS/Androidを両方できる方にジョインしてもらっているため、1.5人くらいのリソースで開発しています。

タスクについては、ライブ配信などの大型施策、KPIを伸ばすためのグロース施策、UI/UXのカイゼンや不具合修正などの改善施策の大きく3つに分かれています。
大型施策は基本的にフルタイムの社員が取り組みますが、それ以外を放置してしまうとサービスとしての質が落ちてしまうので、グロース施策や改善施策についても業務委託の方と協力しながらオーナーシップを持って優先度をつけてバンバン取り組んでいます。

開発環境

MAD scorecardはこちらになります!

f:id:radiotalk-tech:20201222224429p:plain

Android Studio

現在は安定版の最新を使っていますが、betaやalphaも場合によっては使っていた経緯もあります。
これに関しては、リリースされる機能によって開発者体験が向上することをチームで合意が取れれば使っていこうという方針です。

minSdkVersion

21です。これについてはminSdkVersionを上げる基準はまだ明確化できていません。
コンテンツをストックできるサービスでもあるため、慎重に検討しつつ、23に上げるタイミングをはかっています。

Android App Bundle

対応済みです。NDKを扱うSDKもあるため早めに対応をし、かなりアプリサイズの削減ができています。

Jetpack Libraries

積極的に採用しています。主要なものだと、ViewModel/LiveData/WorkManager/Room/Dagger Hilt/Material Componentsなどを使っています。採用できていなものとしてはNavigationがあります。

CI/CD

CIはGitHub Actionsを使っています。プルリクエストに対するktlintの実行と、特定ブランチへのマージをフックにFirebase App DistributionへのAPKのアップを行っています。また、UI崩れがないかというヘルチェックの一環で1日1回Firebase Test Labを走らせています。

QAやリリースのフローは手動で行っているので、折を見て自動化していきたいです。

ドキュメント管理とプロジェクト管理

ドキュメント管理にはKibelaを使っています。施策の素案や詳しい仕様のまとめから、自己紹介や運用ガイドライン、アイデアなど様々なドキュメントを残す文化になっています。

小さいチームなので、タスク管理はスプレッドシートと口頭ベースで行っていました。コロナでのリモート勤務も長引き、少し課題感があったためアイデアベースで提案したところ、Clubhouseというプロジェクト管理ツールを使っています。

f:id:radiotalk-tech:20201222224546p:plain

その他のライブラリ

その他、プロジェクト内でよく使っているライブラリを一部紹介します。

  • ExoPlayer
    • 音声の再生周りを任せています。
  • Groupie
    • RecyclerViewを扱いやすくするライブラリです。好んで使っています。
  • Flipper
    • debugツールです。今年から導入しました。
  • Retrofit/OkHttp/Moshi
    • 通信はsuspend関数で呼び出しています。
  • Glide
    • Coilが気になっていますが今はGlideを全面的に利用しています。
  • MockK
    • テスト時のモックに使っています。
    • 恥ずかしながら最近徐々にテストを書き始めています。
  • RxJava
    • 非同期処理はCoroutines、値の監視はLiveData/Flowに置き換えています。
    • 今のところ、新規の部分で使う予定はなく消す予定です。
  • Kotpref
    • SharedPreferencesを扱いやすくするライブラリです。好んで使っています。
    • DataStoreの動向は横目で見ていますがまだ導入していません。

まとめ

今回は、RadiotalkのAndroidアプリの開発現場についてざっくりと紹介しました! スタートアップでの開発現場が少しでもイメージしていただけたら幸いです。プロダクトを成長させ、最速でユーザーに価値を提供させるためにも、開発現場の改善も日々行っていきます!

今回詳しく説明できなかった部分でもっと聞きたい部分がありましたら、ぜひカジュアルにお話しましょう!気軽に私にDMをするでもOKです!

meety.net

また、RadiotalkではAndroidエンジニアの採用も進めています!
書ききれない部分でカオスな面もあります。一緒にオーナーシップを持ってプロダクトの成長と開発現場のアップデートを推し進めてくれるエンジニアを絶賛募集中です! もしちょっとでも興味がある人は、Wantedlyからまずはカジュアル面談でお話しましょう!

www.wantedly.com

「GCPとPHP」というタイトルでPHP Conference Japan 2020に登壇しました

こんにちは、サーバーサイドエンジニアのサイトウ (@gamu1012) です。

今回はPHP Conference Japan 2020に登壇したことについての振り返りレポートになります。

PHP Conference Japan 2020

https://phpcon.php.gr.jp/2020/

PHPカンファレンスは1999年からPHP関連の技術を主とした技術者カンファレンスです。

登壇内容

「GCPとPHP」というタイトルで発表し、下記のような内容についてお話しました。

  • Google Cloud PlatformでPHP動かす4つの選択肢について
  • オススメの環境について
  • Radiotalkでの事例
  • そこからの学び

全体を通して、

GCPでPHPを動かすときの選択肢を整理するのに使ってもらえれば幸いです。

実際に発表を聞いていただいた方、TwitterやDiscordでコメントや質問をくれた方、ありがとうございました!

GCPについての補足

今回はPHPを動かす環境についてフォーカスして発表しましたが、

GCPは他にもWebアプリケーションを開発/運用するために強力なツールがそろっています。

データベースのCloud SQLやMemorystore、

Cloud Logging, Cloud Monitoringなどのロギングや監視などの運用ツールも素晴らしいです!

皆さんの事例や運用方法もぜひ色々と教えて下さい!

一緒に開発するメンバーを募集しております!

Webと管理ツールの分離やRSS生成をCloud Runに移行するなど、これからもRadiotalkは改善しつづけていきます!

その改善、進化を一緒に楽しんでくれるメンバーを募集しております!

youtrust.jp

カジュアルな知見の情報交換もぜひ!

meety.net

Androidでオーディオアプリを作るということ

こんにちは、Androidアプリエンジニアの牧山(@_rmakiyama)です!
XTechグループ Advent Calendar 2020の4日目を任されました!

先日行われたZli × エキサイト 合同LTに、グループ会社枠としてLTをさせていただきました。
「テーマは技術系ならなんでもOK!」な会だったので、RadiotalkからはAndroidでオーディオアプリを作るにはなにを考えどのように作るかを簡単に説明しました。

10分のLTでは駆け足になったり省略した部分もあるので、発表内容をもとに、よりAndroidエンジニア向けに概要をまとめます。

オーディオアプリの概要

Androidにおけるオーディオアプリを作る際は、UI用のメディアコントローラと実際のプレイヤーを操作するメディアセッションに分割する、クライアント/サーバー型設計が推奨されています。

f:id:radiotalk-tech:20201202185619p:plain

図にもあるように、Androidのmediaライブラリではクライアント/サーバー型設計のアプローチを助けるクラスが準備されています。

メディアセッションとメディアコントローラ

メディアセッションは、サーバーの役割を担うクラスになっています。 プレイヤーの操作は、すべてメディアセッションにより処理するよう実装します。そうすることで、アプリのUIからだけではなくWear OSやAndroid Autoからの操作も一貫した処理を行うことができます。

メディアコントローラは、クライアントの役割を担うクラスになっています。 このクラスを介することにより、UIからはプレイヤーがMediaPlayerなのかExoPlayerなのかといったことを意識することなく操作できます。

オーディオアプリの作成

前述の概要をもとに、ここからは簡単なオーディオアプリの基礎部分の実装方法を紹介します。

f:id:radiotalk-tech:20201202185652p:plain

全体像は上記のようになります。 概要で説明したもの以外にMediaBrowserServiceMediaBrowserが登場しています。

MediaBrowserServiceは、AndroidのServiceを継承しており、MediaSessionとのやりとりを簡略化させてくれます。Serviceを利用することにより、オーディオをバックグラウンドで再生することを可能にしています。

MediaBrowserは、MediaBrowserServiceを介したSessionTokenの取得とMediaSessionが公開するオーディオコンテンツへのアクセスを提供します。

※ 実装にはmedia-compatサポートライブラリを使います。

Serviceの実装

前述の通り、MediaBrowserServiceを用いて実装をしていきます。 onCreate()でメディアセッションを初期化します。

class MyMediaBrowserService : MediaBrowserServiceCompat() {

    private var mediaSession: MediaSessionCompat? = null

    override fun onCreate() {
        super.onCreate()
        mediaSession = MediaSessionCompat(baseContext, TAG).apply {
            // メディアコントローラからの操作をハンドリングする。
            setCallback(callback)
            // 初期化したMediaSessionのTokenをセットする。
            // 接続したクライアントがこのトークンを用いて通信できる。
            setSessionToken(sessionToken)
        }
    }

    // MediaSessionが提供するコールバックをそれぞれ処理する。
    private val callback = object : MediaSessionCompat.Callback() {
        override fun onPlay() {...}
        override fun onStop() {...}
        override fun onPause() {...}
        ...
    }
    ...

MediaSessionCompat.Callbackでは、それぞれ必要に応じてプレイヤーの操作やオーディオフォーカスの制御が必要になります。非常に重要ではありますが、ここでは割愛します。 詳細の実装については公式ドキュメントの実装ガイドを参考にしてください。 また、後述するExoPlayerのExtensionを利用すると、こちらの処理の多くが簡略化されるのでそちらも検討すると良いでしょう。

さて、MediaBrowserServiceでは、クライアントの空の接続を処理するために実装すべきメソッドが2つあります。それが、onGetRoot()onLoadChildren()です。

onGetRoot()では、サービスのアクセス制御を行います。こちらはアプリケーションの要件に応じて処理していきます。

    ...

    override fun onGetRoot(
        clientPackageName: String,
        clientUid: Int,
        rootHints: Bundle?
    ): BrowserRoot? {
        return if (allowBrowsing(clientPackageName, clientUid)) {
            // ブラウジングを許可する場合はコンテンツ階層を表すルートIDを返す
            BrowserRoot("root", null)
        } else {
            // 接続を拒否する場合はnullを返す
            null
        }
    }

    ...

クライアントからは、MediaBrowserCompat#subscribeによりコンテンツを走査できます。このメソッドからonLoadChildren()コールバックが呼ばれ、適切なコンテンツを返すことでクライアントはUIを構築できます。
ここで返したコンテンツのリストは、MediaBrowserCompat#subscribeを呼び出すときに渡せるSubscriptionCallbackでコールバックとして流れてきます。

    ...

    override fun onLoadChildren(
        parentId: String,
        result: Result<MutableList<MediaItem>>
    ) {
        // parentIdに応じたMediaItemのリストを取得
        val mediaItems: MutableList<MediaItem> = getMediaItems(parentId)
        result.sendResult(mediaItems)
    }

    ...

MediaBrowserの実装

MediaBrowserは、MediaBrowserServiceとの接続とMediaControllerの生成の役割を担っています。

まずはMediaBrowserServiceとの接続を実装していきます。 MediaBrowserは生成時に、接続するServiceの名前を指定します。 connect()を呼ぶことでMediaBrowserServiceに接続できます。

class MediaPlayerActivity : AppCompatActivity() {

    private lateinit var mediaBrowser: MediaBrowserCompat
    private lateinit var mediaController: MediaControllerCompat

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        mediaBrowser = MediaBrowserCompat(
            this,
            ComponentName(this, MyMediaBrowserService::class.java),
            connectionCallbacks,
            null
        )
        mediaBrowser.connect()
    }
    ...

接続が完了すると、MediaBrowserの生成時に渡していたコールバックが呼ばれます。 接続後にはMediaBrowserを通してセッショントークが取得できるため、それを用いてMediaControllerを生成します。

    ...

    private lateinit var mediaController: MediaControllerCompat
    ...

    private val connectionCallbacks = object : MediaBrowserCompat.ConnectionCallback() {
        override fun onConnected() {
            // 接続したMediaSessionのトークンを取得することができる
            mediaBrowser.sessionToken.also { token ->
                // トークンを使ってMediaControllerを生成
                mediaController = MediaControllerCompat(
                    this@MediaPlayerActivity,
                    token
                )
            }
            // コンテンツの情報と再生の状態をコールバックで受け取る
            mediaController.registerCallback(controllerCallback)
        }

        // 必要に応じて実装
        override fun onConnectionSuspended() {}
        override fun onConnectionFailed() {}
    }
    ...

あとは生成されたMediaControllerCompatTransportControlsを通してオーディオを再生するだけです。ここで大事なのは、このMediaPlayerActivityではMediaPlayerやExoPlayerといったプレイヤーに依存していないということです。どのように再生するかをMediaSessionが一貫して担うことで、Wear端末などからの操作でも同じように振る舞うことができます。

※ 今回例示した実装ではActivityにそのまま実装をしていますが、実際のプロダクトではアーキテクチャに応じてViewModelやドメインサービス等に実装をすると良いでしょう。

オーディオアプリとしての振る舞い

ここまでの説明に含まれていませんが、必ず考慮すべき点がまだあります。

  • オーディオフォーカスの管理
  • 音声出力の変更の処理
  • 再生中の通知の表示

オーディオフォーカスの管理は、再生中に他のアプリで音声を再生した場合の振る舞いを制御するために必要です。ここを無視すると、自分のアプリが別のアプリの音声とかぶってしまい、ユーザーにとって非常に悪い体験になってしまいます。
音声出力の変更の処理は、再生中にイヤホンが抜けた場合などの振る舞いを制御するために必要です。イヤホンが抜けて聴いていたものが大音量で流れてしまわないためにも必要です。 再生中の通知の表示は、バックグラウンドで動作するアプリには必須の要件です。

これらを愚直に実装するのはなかなか骨が折れます。これらをExoPlayerを用いて簡略化する方法を次に紹介します。

ExoPlayer用いた実装

ExoPlayerはGoogle製のライブラリで、MediaPlayerが提供していないDASHなどの再生APIもサポートしています。
2020/12/04時点での最新バージョンは2.12.2です。

ExoPlayerでは2.9.0からオーディオフォーカスのハンドリングを、2.11.0から音声出力の変更の処理のハンドリングをサポートしています。
そのため、以下のように初期化するのみで対応できるようになりました。

private val exoPlayer: ExoPlayer by lazy {
    SimpleExoPlayer.Builder(context).build().apply {
        val attr = AudioAttributes.Builder()
            .setUsage(C.USAGE_MEDIA)
            .setContentType(C.CONTENT_TYPE_MUSIC)
            .build()
        setAudioAttributes(attr, true)
        setHandleAudioBecomingNoisy(true)
    }
}

さらに、ExoPlayerライブラリの中のexoplayer-uiにはPlayerNotificationManagerというクラスが含まれています。詳細を省きますが、これを利用することで通知チャンネルの生成や再生中の音声の情報を用いた通知の構築などを切り出せるので、実装の見通しがかなり良くなります。

ExoPlayer Extension

ExoPlayerはさまざまなExtensionを提供しています。その中にあるMediaSession extensionを利用することで、MediaSessionとExoPlayerのやりとりをかなり簡略化できます。

前述の通り、実際に音声を再生するプレイヤーの制御はMediaSessionCompat.Callback()を実装してそれぞれ操作する必要がありました。MediaSession extensionではMediaSessionConnectorMediaSessionConnector.PlaybackPreparerというクラスを提供しており、MediaSessionとの統合がかなり簡単になります。
これらの実際の実装は、公式の紹介ブログGoogleによるオーディオアプリのサンプルを参考にしてください。

最後に

今回は、Androidでオーディオアプリを作成する基礎部分について紹介しました。最初はとっつきにくいですが、一度実装してみると理にかなった設計だと感じます。またExoPlayerとそのExtensionを使うことでさらに便利になりました。
音声や動画を扱うアプリに携わらないと知れない領域でもあるので、これを機に興味を持っていただけたら幸いです!

RadiotalkではAndroidエンジニアも絶賛募集しております!
もしちょっとでも興味がある人は、Wantedlyからまずはカジュアル面談でお話しましょう! 気軽に私にDMをするでもOKです!

www.wantedly.com

参考

iOSDC Japan 2020にRadiotalkメンバーが登壇します

iOSDC Japan 2020

開催日時 : 2020年9月19日(土)〜9月21日(月・祝)

開催場所 : オンライン開催

公式サイト : https://iosdc.jp/2020/index.html

公式Twitter : https://twitter.com/iosdcjp

登壇メンバー

Radiotalkから2名が登壇します!

両名とも音声に関する内容になっておりますので、

iOSでの音声関連の実装に興味あるかた、音声配信サービスを運営している方はぜひご覧ください!

2020/09/19 18:30〜 今日から分かるAVAudioEngineの全

fortee.jp

2020/09/21 15:40〜 実装したくなる音声編集

fortee.jp

2020/09/21 17:25〜 Apple Low-Latency HLSを使った超低遅延配信について

fortee.jp

最後に

RadiotalkではiOSアプリエンジニアを募集しており、

もしちょっとでも興味がある人は下記のページからご応募いただくか、

Twitter等のSNSでお声かけいただければと思います!

herp.careers


グループ会社のエキサイトはシルバースポンサーとして協賛をしておりますので、そちらもご確認ください。

exdev.exblog.jp

インターン体験談 #Androidチーム編

f:id:radiotalk-tech:20200712113532p:plain

こんにちは!

2020年2月中旬から4ヶ月ほど、AndroidチームでインターンさせていただいたGo(@TwisedGoap)です!

本記事では、RadiotalkのAndroidチームで体験したこと・成長できたことを紹介します。

最初のタスクはデザイン修正

最初のタスクは「タグ一覧のリストデザイン修正」でした。

具体的には、次のことをしました。

  • アイコンを角丸にする
  • 「#」マークをアイコンの右上に設置する

以下の画像が、実際に修正した様子です。

f:id:radiotalk-tech:20200711142335p:plain
タグ一覧のリストデザイン修正

Kotlinのコードを書くよりも、XMLのコードを修正する方が比較的簡単だろうと思って着手しましたが、全然そんなことはありませんでした😅

その理由は、次のような細かいルールに則る必要があったからです。

@deminの値を使って、レイアウト崩れを防止する

XMLで4dp上にマージンが欲しいときは、次のように書くと思います。

android:layout_marginTop="4dp"

しかし、Radiotalkでは次のように書きます。

android:layout_marginTop="@dimen/space_4dp"

// dimens.xml
<dimen name="space_4dp">4dp</dimen>

どちらも4dp上にマージンを指定しているのは変わりませんが、Radiotalkではdimens.xmlに定義した値を使っています。

一見すると回りくどく見えますが、「アプリで使えるスペース幅は決まっている」という意識づけがされます。

デザインスペックに合わせる

デザインスペックとは、デザイン上の幅や色を細かくみるためもので、RadiotalkではFigmaで共有されています。

エンジニアはデザインスペックにできるだけ従って実装していきます。

全然難しいことはなさそうですが、僕にとっては結構大変でした。

それは、XMLとデザインスペックを照らし合わせて確認作業をしていたからです。

そのため確認に時間がかかりすぎたり、見落としが多くなってしまいました。

これを解消してくれたのは、Window VQAというアプリでした。

Window VQAはスマホ画面上であらゆるViewのサイズを測ることができます。

f:id:radiotalk-tech:20200711160715p:plain
Window VQAの動作イメージ

Window VQAを知ってからは、デザインの確認がスムーズになりました。

アーキテクチャを理解する

インターン開始から1ヶ月ほど経ち、一通りの開発フローをこなせるようになったところで、アプリ全体に関わるタスクを着手するようになりました。

そして、このインターンでの最大の壁に直面します。

それは「アプリのアーキテクチャの理解」です。

Hello, マルチモジュール!

RadiotalkのAndroidアプリはマルチモジュールのプロジェクトになっていて、以下のようなモジュール分けがされています(※大部分が省略されています)

また、モジュール内・モジュール間の具体的な実装は、Layered Architectureに従っています。

f:id:radiotalk-tech:20200704121703p:plain
モジュール分けの概要図

私自身、MVVM, Layered Architectureなど用語は知っているつもりでしたが、実践したことはなかったので、なかなか慣れるまでに時間がかかりました。

「Androidの会」でキャッチアップ力をつける

RadiotalkのAndroidチームでは、週1で「Androidの会」を実施して、Androidに関する情報共有をしていました。

私がインターンをしていた時期は、Android11の新情報がたくさん出ていた時期であり、Androidの会でもよくトピックになりました。

f:id:radiotalk-tech:20200711175552p:plain
Androidの会のissueにコメントする

Androidの会で共有したいことは予めGitHubのissueにコメントしておく運用でした。

そのため、前にコメントしている人とトピックが被らないようにしたくなります(別に被ってもよかったけど😅)

自分はいつも後からコメントしていたので、いつも違うトピックを見つけてくるのに躍起になっていました。

そのおかげか、

  • Androidの良い情報が集まる場所はどこなのか
  • Androidの最新情報の出どころはどこなのか

などがわかるようになり、自ずとキャッチアップ力が向上しました。

さいごに

Radiotalkでのインターンを経て、自分は「Android好きな学生」から「Androidエンジニア」にレベルアップさせてもらったと感じでいます。

また施策に取り組む中で、自分の目指したいエンジニア像が変わり、

明確に「グロースエンジニア」を目指していきたいと思うようになりました。

また、新しい開発現場でもすぐに施策にアサインできているのはRadiotalkでの経験のおかげに他なりません。

改めて、Radiotalkの皆さんに感謝申し上げます。

本当にありがとうございました!

Flipperを導入してデバッグを快適に

こんにちは、Androidエンジニアの牧山(@_rmakiyama)です。

Radiotalkでは、特にAPI通信周りの確認にokhttp3logging-interceptorを使っていました。
どのエンドポイントを呼び、結果がどうだったかなどを確認する上では十分だったのですが、細かくレスポンスの中身を見たい、APIの呼ばれた順序をひと目で確認したいというニーズが高まりました。

タイミングよく、DroidKaigi 2020 - 1から学ぶAndroidアプリデバッグ - アプリの動作を追いかけよう / Yoshihiro Wada [JA]の発表を見て、デバッグツールの便利さを改めて知り、Flipperを採用したのでご紹介します。

Flipperとは

公式から引用すると

Flipper is a platform for debugging iOS, Android and React Native apps. Visualize, inspect, and control your apps from a simple desktop interface. Use Flipper as is or extend it using the plugin API.

つまり、モバイル用のデバッグツールです。Android専用ではないところが驚きでした。

ちなみにiOSでは物理デバイスのサポートはされていないようなので、RadiotalkではiOSでの導入は見送りました。

他の選択肢

他にもstethoHyperion-Androidなどがあります。

今回は主にNetwork周りのデバッグを楽にしたいというニーズと、比較的導入が簡単なFlipperを選びました。

Flipperを導入して良かったところ

まずは導入方法の前に良かったところを。

ネットワーク通信のログをLogcatを探らず見れる

どのエンドポイントにリクエストしてレスポンスが何かをGUIで簡単に見れて素敵でした。 一覧性があるのでどの順番で呼んだかなども見やすくなりました。

API呼び出し一覧

|f:id:radiotalk-tech:20200626155506p:plain

APIレスポンス

f:id:radiotalk-tech:20200626155522p:plain|

Roomデーターベースの中身が覗ける

f:id:radiotalk-tech:20200626155621p:plain

デバッグのときはかなり便利。クエリも書けちゃいます。

SharedPreferenceの中身が覗ける

f:id:radiotalk-tech:20200626155637p:plain

Android Studio上からも見れますが意外と面倒ですよね…これも便利です。値の変更もできます。

Flipper導入方法

公式のSet up your Android appに沿って説明していきます。

今回は紹介したNetworkプラグインを含めた導入方法になります。

Dependencies

複数のビルドバリアントが存在する場合は各バリアントに合わせた依存を定義します。

debugImplementation "com.facebook.flipper:flipper:${versions.flipper}"
debugImplementation "com.facebook.soloader:soloader:${versions.soloader}"
debugImplementation "com.facebook.flipper:flipper-network-plugin:${versions.flipper_network}"
stagingImplementation "com.facebook.flipper:flipper:${versions.flipper}"
stagingImplementation "com.facebook.soloader:soloader:${versions.soloader}"
stagingImplementation "com.facebook.flipper:flipper-network-plugin:${versions.flipper_network}"

releaseImplementation "com.facebook.flipper:flipper-noop:${versions.flipper}"

Application Setup

releaseImplementation'com.facebook.flipper:flipper-noop'を入れましたが、プラグインに関してはnoopを提供しない予定なようなので、リリース版とその他版でApplicationをわけるのが良さそうです。

本番のApplicationをopenクラスにしつつ

open class App : DaggerApplication() {

    override fun onCreate() {
        super.onCreate()
        // setups...
    }

他のバリアントでは、元のApplicationを継承しつつFlipperをセットアップしましょう。

class DebugApp : App() {

    override fun onCreate() {
        super.onCreate()
        initializedFlipper()
    }

    private fun initializedFlipper() {
        SoLoader.init(this, false)
        if (BuildConfig.DEBUG && FlipperUtils.shouldEnableFlipper(this)) {
            val client = AndroidFlipperClient.getInstance(this).apply {
                addPlugin(NetworkFlipperPlugin())
                addPlugin(SharedPreferencesFlipperPlugin(this@DebugApp))
                addPlugin(DatabasesFlipperPlugin(this@DebugApp))
            }
            client.start()
        }
    }
}

Diagnostics

公式のとおりこちらも追加しておきます。

<activity android:name="com.facebook.flipper.android.diagnostics.FlipperDiagnosticActivity"
        android:exported="true"/>

Enabling plugins

公式サイトからデスクトップアプリケーションをダウンロードして、追加したプラグインを有効にすれば完了です!

Links

輪読会のすゝめ

こんにちは、サーバーサイドエンジニアの サイトウ (@gamu1012) です。

今回はエキサイト社とRadiotalk社の合同で行っている輪読会について紹介します。

輪読会とは

輪読会

人々が集まって、同じ教科書などの本を読み、その内容について意見を交わすことを意味する語。事前に決められた担当者が、本の内容を訳したりまとめたりしてから、他の参加者が理解できるように発表する形式がとられることも多い。

Weblio辞書より引用

我々が行っている輪読会は下記の特徴があります。

  • 全員が本を購入
  • 章ごとに行う
  • 事前に該当章を全員が読んでくる
  • 議論中心
  • 週に1回1時間程度

誰とやっているか

エキサイト社とRadiotalk社のエンジニア合同でやっており、現在は8人で進めています。

年次は新卒入社1年目から中堅まで、専門領域や担当サービスなどもバラバラです。

本を選ぶ

上述のとおり、メンバーは領域、経験がバラバラなため、下記の2点を意識しています。

  • 極端に簡単であったり、極端に専門的な内容は選ばない
  • 偏った領域の本は選ばない

1冊が完了したときに、みんなで次はこれがいいと思う!というのをリストアップし、合議で決めるという流れで行っています。

2点を意識すると設計の話になりがちで、もし輪読会をやっているよ!という方がいらっしゃいましたら、オススメの本を教えて下さい!

進め方

事前準備

  • 全員が購入済み
  • 章ごとに担当を割りふる
  • 担当は概要と気になった点をまとめる

当日のフロー

担当から概要の共有

それぞれが"気になった点"をあげる

議論

以下、くりかえしで1時間ほど、盛り上がったときは1時間30分ほどの議論します。

"気になった点"について

  • 私はここが気になった
  • ここがいまいち理解できなかったので、どなたかもう少し解説してほしい
  • ここが"エモい"

議論の種は幅広くあげ、それぞれの意見を交換することを心がけています。

輪読会のなにがいいか

本の内容と実体験を交えた話ができるため、学びが深くなるというのが一番素晴らしい点だと感じています。

経験があるメンバーは実体験を踏まえた、意見をルーキーのメンバーに共有しながら、

設計パターンやテクニックついて、メリットデメリットを議論できたり、

ルーキーのメンバーから出た質問に全員がハッとすることも多々あり、いいサイクルがまわります。

まとめ

輪読会は議論を通すことで、学びが深くなり、素晴らしいです。

XTechグループは輪読会をはじめ、AWSGCPなどのクラウド勉強会もやっております。

その話はまた別の機会にさせていただきます。

参考

主催メンバーからみた輪読会はこちら。

ohshige.hatenablog.com

ohshige.hatenablog.com

いつもありがとうございます!

輪読した本