こんなサンプルアプリを作りました。 YoutubeDataAPIでYoutube動画を検索・再生できる簡単なアプリです。
ソースコードはこちら。
雑なところ色々あるのですが、、、そこは追々直していきます。💦
使ったライブラリ
API,非同期通信系
- Retrofit2
- rxJava2
- Moshi
- OKHttp3
この辺はおなじみのやつって感じですね。 いいかげんCoroutine覚えなければなーとは思ってます。
他
- Timber(ログ)
- Glide(画像)
- Koin(DI)
- Epoxy(RecyclerView)
- ExoPlayer(動画プレイヤー)
- android-youtubeExtractor
- Youtube動画をExoPlayerで再生できる形式に変換するために用います
アーキテクチャ
そんなにしっかり分けてもないんですが、MVVMにしています。(しているつもり)
1画面のみのあっさりしたサンプルアプリなので、Fragmentは使ってません。
View(Activity)
UI周りの処理を担当。
Model
データ周り。
ViewModel
ActivityとModelをつなぐ。 RepositoryやLiveDataはここで。
実装
サービスの生成
interface YoutubeApiService { @GET("youtube/v3/search") fun getYoutubeData( @Query("key") key: String, @Query("type") type: String, @Query("part") part: String, @Query("q") q: String, @Query("maxResults") maxResults: Int ): Single<YoutubeDataResponse> companion object : KoinComponent { fun create(): YoutubeApiService { val baseUrl = "https://www.googleapis.com/" val okhttpClient = OkHttpClient.Builder() .build() val retrofit = Retrofit.Builder() .addConverterFactory( MoshiConverterFactory.create( Moshi.Builder() .add(KotlinJsonAdapterFactory()) .build() )) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .baseUrl(baseUrl) .client(okhttpClient) .build() return retrofit.create(YoutubeApiService::class.java) } } }
interfaceとして定義します。このYoutubeApiServiceをRepository内で生成してViewModelにはRepositoryを渡します。
ViewModel
repositoryはKoinで注入しています。
class MainViewModel : ViewModel(), KoinComponent { val repository: YoutubeApiRepository by inject() val disposable: CompositeDisposable = CompositeDisposable() val jsonKeys: String = BuildConfig.API_KEY val youtubeDataResponse: MutableLiveData<YoutubeDataResponse> = MutableLiveData() fun getYoutubeData(q: String) { disposable.addAll( repository.getYoutubeData(jsonKeys, q).subscribeOn(Schedulers.io()).subscribe({ youtubeDataResponse.postValue(it) }, { Timber.e(it) }) ) } override fun onCleared() { super.onCleared() disposable.clear() } }
Activity
もうちょっとスッキリさせたい感はするんですが、、UI周りをメインに実装します。
class MainActivity : AppCompatActivity(), MainController.ClickLister { val youtubeLink: String = "http://youtube.com/watch?v=" var youTubeVideoID: String = "" var parseUrl: String = "" private val appName: String = "youtubeplayer" private var playerManager: PlayerManager? = null val viewModel: MainViewModel by viewModel() lateinit var controller: MainController lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = DataBindingUtil.setContentView(this, R.layout.activity_main) binding.lifecycleOwner = this observeItem() controller = MainController(this) recycle_view.adapter = controller.adapter recycle_view.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false) searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String?): Boolean { viewModel.getYoutubeData(query!!) return true } override fun onQueryTextChange(newText: String?): Boolean { return false } }) } private fun observeItem() { viewModel.youtubeDataResponse.observeForever { controller.setData(it.items) } } fun dlYoutube() = object : YouTubeExtractor(this) { override fun onExtractionComplete(ytFiles: SparseArray<YtFile>, vMeta: VideoMeta) { if (ytFiles != null) { parseUrl = ytFiles.get(ytFiles.keyAt(0)).url setPlayer() } } }.extract(youtubeLink + youTubeVideoID, true, true) fun setPlayer() { if (playerManager != null) playerManager?.release() playerManager = PlayerManager(this, appName) playerManager?.playserNewInstatnce() playerManager?.buildMediaSource(Uri.parse(parseUrl), null) playerManager?.playerView = playerView playerManager?.execute() } override fun onItemClickListener(item: Item) { if (youTubeVideoID == item.id.videoId) return youTubeVideoID = item.id.videoId dlYoutube() } override fun onStop() { super.onStop() playerManager?.stop() } override fun onResume() { super.onResume() playerManager?.replay() } }
他には、ExoPlayerのためのPlayerManagerなども実装しているのですが、ExoPlayerについては別記事にしてまとめようと思います。
機会があれば、今度はこれにpagingを実装したり、マルチモジュール化したりして遊んでみたいなと思ってます。
その時はまたブログにすると思います〜〜 ( ´ ▽ ` )ノシ