Main Activity
Let’s create our menu items:
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item
android:enabled="true"
android:icon="@drawable/ic_home"
android:menuCategory="system"
android:title="@string/nav_home"
android:tooltipText="@string/nav_home"
app:showAsAction="always" />
<item
android:enabled="true"
android:icon="@drawable/ic_plus"
android:menuCategory="system"
android:title="@string/nav_record"
app:showAsAction="always" />
<item
android:enabled="true"
android:icon="@drawable/ic_profile"
android:menuCategory="system"
android:title="@string/nav_profile"
android:tooltipText="@string/nav_profile"
app:showAsAction="always" />
</menu>
Now our layouts, note that we will fill our Recycler View with different flavor binds. Exo Player for gms build and HMS Video Kit for our hms builds:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
tools:context=".ui.activities.MainActivity">
<include
android:id="@+id/lottie_inc"
layout="@layout/lottie_loading"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/video_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:elevation="3dp"
app:layout_constraintBottom_toTopOf="@+id/view"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/layout_video" />
<View
android:id="@+id/view"
android:layout_width="match_parent"
android:layout_height="2dp"
android:background="@color/white"
app:layout_constraintBottom_toTopOf="@+id/animatedBottomBar" />
<nl.joery.animatedbottombar.AnimatedBottomBar
android:id="@+id/animatedBottomBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/colour_bg"
app:abb_indicatorAppearance="round"
app:abb_indicatorColor="@color/white"
app:abb_indicatorHeight="4dp"
app:abb_indicatorLocation="bottom"
app:abb_indicatorMargin="16dp"
app:abb_rippleColor="@color/lightGray"
app:abb_rippleEnabled="true"
app:abb_selectedIndex="0"
app:abb_selectedTabType="text"
app:abb_tabAnimation="slide"
app:abb_tabAnimationSelected="slide"
app:abb_tabColorSelected="@color/white"
app:abb_tabs="@menu/bottom_bar"
app:abb_textAppearance="@style/BottomBarItemText"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
Creating Record Buttons elements:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:layout_gravity="bottom">
<LinearLayout
android:id="@+id/front_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:visibility="visible"
android:background="@drawable/dialog_bottom_background"
android:orientation="vertical"
android:padding="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:weightSum="1">
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".2"
android:contentDescription="@string/image"
android:src="@drawable/ic_web" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dialog_input_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".6"
android:hint="@string/enter_url_address_for_a_video"
android:textColorHint="@color/white"
app:boxStrokeColor="@color/white"
app:boxStrokeWidth="1dp"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:hintTextColor="@color/white">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dialog_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/roboto"
android:inputType="textUri"
android:textColor="@color/white"
android:textColorHint="@color/white" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/dialog_flip"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/pick_url"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:src="@drawable/ic_flip"/>
<ImageButton
android:id="@+id/dialog_send_url"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/send_url"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:src="@drawable/ic_send" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/darker_light_fuchsia" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="horizontal"
android:visibility="gone"
android:weightSum="1">
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".2"
android:contentDescription="@string/image"
android:src="@drawable/ic_yt_logo_2" />
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/dialog_yt_input_layout"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".7"
android:hint="@string/enter_yt_url_address_for_a_video"
android:textColorHint="@color/white"
app:boxStrokeColor="@color/white"
app:boxStrokeWidth="1dp"
app:errorEnabled="true"
app:hintAnimationEnabled="true"
app:hintEnabled="true"
app:hintTextColor="@color/white">
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/dialog_yt_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/roboto"
android:inputType="textUri"
android:textColor="@color/white"
android:textColorHint="@color/white" />
</com.google.android.material.textfield.TextInputLayout>
<ImageButton
android:id="@+id/dialog_yt_send_url"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/send_url"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:src="@drawable/ic_send" />
</LinearLayout>
<View
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/darker_light_fuchsia"
android:visibility="gone" />
<LinearLayout
android:id="@+id/dialog_video"
android:layout_width="match_parent"
android:layout_height="70dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:orientation="horizontal"
android:weightSum="1">
<ImageView
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".2"
android:contentDescription="@string/image"
android:src="@drawable/ic_video" />
<TextView
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight=".8"
android:fontFamily="@font/roboto"
android:gravity="center_vertical"
android:paddingStart="15dp"
android:paddingEnd="0dp"
android:text="@string/record_video"
android:textColor="@color/white" />
</LinearLayout>
</LinearLayout>
<LinearLayout
android:id="@+id/back_record"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:background="@drawable/dialog_bottom_background"
android:orientation="vertical"
android:padding="10dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/close_circle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:clickable="true"
android:contentDescription="@string/close"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:src="@drawable/ic_flip"
app:civ_border_color="@color/huawei_red"
app:civ_circle_background_color="@color/huawei_red" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/dialog_recycler"
android:layout_width="match_parent"
tools:listitem="@layout/single_video_item"
android:layout_height="160dp"/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

We’ve talked about adding predefined items. Now let’s build the item to create their process for recycler:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools"
android:background="@drawable/colour_orange_bg"
android:orientation="horizontal"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:padding="5dp"
android:layout_margin="5dp"
android:weightSum="2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="0dp"
android:layout_weight=".2"
android:layout_gravity="center_vertical"
android:layout_height="32dp"
android:src="@drawable/video_stock" />
<LinearLayout
android:layout_width="0dp"
android:layout_weight="1.5"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/s_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/white"
android:fontFamily="@font/bree_serif"
android:maxEms="1"
tools:text="Video Name"
android:ellipsize="end"/>
<TextView
android:id="@+id/s_url"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:textColor="@color/white"
android:fontFamily="@font/phenomena_regular"
android:maxEms="1"
tools:text="Video Url"
android:ellipsize="end"/>
</LinearLayout>
<TextView
android:id="@+id/s_total"
android:layout_width="0dp"
android:layout_weight=".3"
android:textColor="@color/white"
android:fontFamily="@font/phenomena_regular"
android:layout_height="32dp"
android:gravity="center"
android:layout_gravity="center_vertical"
tools:text="00:00"
android:src="@drawable/video_stock" />
</LinearLayout>
Preview of it:

And calling it to their Adapter:
class MainVideoItemAdapter(context: Context, activity: MainActivity) :
RecyclerView.Adapter<MainVideoItemAdapter.ViewHolder>() {
private val act = activity
private val cntx = context
private val dataUrlArray = context.resources.getStringArray(R.array.data_url)
private val dataNameArray = context.resources.getStringArray(R.array.data_name)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.single_video_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
holder.bindVidItem(dataNameArray[position], dataUrlArray[position])
val scale = act.applicationContext.resources.displayMetrics.density
act.dFrontLyt.cameraDistance = 8000 * scale
act.dBackLyt.cameraDistance = 8000 * scale
val frontAnim =
AnimatorInflater.loadAnimator(
cntx,
R.animator.front_animator
) as AnimatorSet
val backAnim = AnimatorInflater.loadAnimator(
cntx,
R.animator.back_animator
) as AnimatorSet
holder.parent.setOnClickListener {
act.dUrl.setText(dataUrlArray[position])
FlipCard.flipBackAnimator(frontAnim, act.dFrontLyt, backAnim, act.dBackLyt)
}
}
override fun getItemCount(): Int {
return dataUrlArray.size
}
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val parent = itemView
var vidName: TextView
var vidUrl: TextView
init {
vidName = parent.findViewById(R.id.s_name)
vidUrl = parent.findViewById(R.id.s_url)
}
fun bindVidItem(vid_name: String, vid_url: String) {
vidName.text = vid_name
vidUrl.text = vid_url
}
}
}

Now for the Main’s interface:
class IMain {
interface ViewMain{
fun setupDialog(context: Context,type:Int)
fun setupRecycler()
fun checkItems()
}
}
Then for its presenter:
class MainPresenter {
fun gotoProfile(context: Context) {
context.startActivity(Intent(context, ProfileActivity::class.java))
}
fun gotoRecord(context: Context) {
context.startActivity(Intent(context, RecordActivity::class.java))
}
fun restart(context: Context) {
context.startActivity(Intent(context, MainActivity::class.java))
}
}
Finally, we can begin for building activity:
class MainActivity : AppCompatActivity(), IMain.ViewMain {
private lateinit var binding: ActivityMainBinding
lateinit var dialog: Dialog
lateinit var dUrl: TextInputEditText
lateinit var dFrontLyt: LinearLayout
lateinit var dBackLyt: LinearLayout
private val presenter: MainPresenter by lazy { MainPresenter() }
…
}
Let’s start by filling overridden methods and custom ones:
- setupDialog(…)
override fun setupDialog(context: Context, type: Int) {
dialog = Dialog(context, R.style.BlurTheme)
dialog.window!!.attributes.windowAnimations = type
dialog.setContentView(R.layout.dialog_record)
dialog.setCanceledOnTouchOutside(true)
val dRecord = dialog.findViewById<LinearLayout>(R.id.dialog_video)
val dUrlLyt = dialog.findViewById<TextInputLayout>(R.id.dialog_input_layout)
dUrl = dialog.findViewById(R.id.dialog_url)
val dUrlBtn = dialog.findViewById<ImageButton>(R.id.dialog_send_url)
dFrontLyt = dialog.findViewById(R.id.front_record)
dBackLyt = dialog.findViewById(R.id.back_record)
val circleFlip = dialog.findViewById<CircleImageView>(R.id.close_circle)
val dFlip = dialog.findViewById<ImageButton>(R.id.dialog_flip)
val dRecycler = dialog.findViewById<RecyclerView>(R.id.dialog_recycler)
dRecycler.layoutManager = LinearLayoutManager(this)
dRecycler.adapter = MainVideoItemAdapter(this, this)
dRecycler.setHasFixedSize(true)
val scale = this.applicationContext.resources.displayMetrics.density
dFrontLyt.cameraDistance = 8000 * scale
dBackLyt.cameraDistance = 8000 * scale
val frontAnim =
AnimatorInflater.loadAnimator(
this,
R.animator.front_animator
) as AnimatorSet
val backAnim = AnimatorInflater.loadAnimator(
this,
R.animator.back_animator
) as AnimatorSet
dFlip.setOnClickListener {
FlipCard.flipFrontAnimator(frontAnim, dFrontLyt, backAnim, dBackLyt)
circleFlip.setOnClickListener {
FlipCard.flipBackAnimator(frontAnim, dFrontLyt, backAnim, dBackLyt)
}
}
dUrlBtn.setOnClickListener {
showLogInfo(Constants.mMainActivity, "Passing: " + dUrl.text.toString())
val url = dUrl.text.toString()
if (UrlValidatorHelper.isValidUrl(url))
startActivity(
Intent(this, SinglePlayerActivity::class.java)
.putExtra("url", url)
.putExtra("type", 0)
)
else
dUrlLyt.error = getString(R.string.error_url)
}
dRecord.setOnClickListener {
dialog.dismiss()
presenter.gotoRecord(this)
}
dialog.show()
}
checkItems(), Check if main recycler has items:
override fun checkItems(){
Constants.fSharedRef.addValueEventListener(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
if (snapshot.hasChildren()){
binding.lottieInc.root.visibility = View.GONE
binding.videoRecycler.visibility = View.VISIBLE
} else{
binding.lottieInc.root.visibility = View.VISIBLE
binding.videoRecycler.visibility = View.GONE
}
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity,error.toString())
}
})
}


- handle permissions:
private fun requestPermissions() {
ActivityCompat.requestPermissions(
this,
arrayOf(
Manifest.permission.INTERNET,
Manifest.permission.CHANGE_NETWORK_STATE,
Manifest.permission.CHANGE_WIFI_STATE,
Manifest.permission.RECORD_AUDIO,
Manifest.permission.READ_EXTERNAL_STORAGE,
Manifest.permission.ACCESS_WIFI_STATE,
Manifest.permission.CAMERA,
Manifest.permission.WRITE_EXTERNAL_STORAGE
),
2020
)
}
- onStart:
override fun onStart() {
super.onStart()
setupRecycler()
checkItems()
}
- onCreate():
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(R.style.Theme_ExoVideoReference_TransStatusBar)
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.hide()
requestPermissions()
binding.animatedBottomBar.setOnTabSelectListener(object :
AnimatedBottomBar.OnTabSelectListener {
override fun onTabSelected(
lastIndex: Int,
lastTab: AnimatedBottomBar.Tab?,
newIndex: Int,
newTab: AnimatedBottomBar.Tab
) {
when (newIndex) {
/* Home */
0 -> {
presenter.restart(this@MainActivity)
}
/* Record */
1 -> {
setupDialog(this@MainActivity, R.style.BlurTheme)
}
/* Profile */
2 -> {
presenter.gotoProfile(this@MainActivity)
}
}
}
override fun onTabReselected(index: Int, tab: AnimatedBottomBar.Tab) {
super.onTabReselected(index, tab)
when (index) {
0 -> {
presenter.restart(this@MainActivity)
}
1 -> {
setupDialog(this@MainActivity, R.style.BlurTheme)
}
2 -> {
presenter.gotoProfile(this@MainActivity)
}
}
}
})
binding.videoRecycler.layoutManager = LinearLayoutManager(this)
binding.videoRecycler.setHasFixedSize(true)
}
- Now I have left setting up recycler to the end because we didn’t create any flavor build bindings yet, but still, I’ll provide it in this section keep in mind that we’ll fill its missing part once we move to Exo Player and HMS Video Kit.
override fun setupRecycler() {
showLogDebug(Constants.mMainActivity, "fSharedRef: ${Constants.fSharedRef}")
val options = FirebaseRecyclerOptions.Builder<DataClass.UploadsShareableDataClass>()
.setQuery(Constants.fSharedRef, DataClass.UploadsShareableDataClass::class.java).build()
val adapterFire = object :
FirebaseRecyclerAdapter<DataClass.UploadsShareableDataClass, HmsGmsVideoViewHolder>(
options
) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): HmsGmsVideoViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.layout_video, parent, false)
return HmsGmsVideoViewHolder(view)
}
override fun onBindViewHolder(
holder: HmsGmsVideoViewHolder,
position: Int,
model: DataClass.UploadsShareableDataClass
) {
val lisResUid = getRef(position).key
var lovely = model.like.toInt()
holder.sLovely.setOnClickListener {
lovely++
FirebaseDbHelper.getShareItem(lisResUid!!).child("like")
.setValue(lovely.toString())
}
holder.sLovely.text =
getString(R.string.lovely_counter, NumberConvertor.prettyCount(lovely))
FirebaseDbHelper.getPostMessageRef(lisResUid!!)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
holder.bindComments(NumberConvertor.prettyCount(snapshot.childrenCount))
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity, error.toString())
}
})
FirebaseDbHelper.getShareItem(lisResUid)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val senderID = snapshot.child("uploaderId").value.toString()
FirebaseDbHelper.getUserInfo(senderID)
.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val pName = snapshot.child("nameSurname").value.toString()
holder.bindDialogInfo(
model.videoUrl,
pName,
model.uploaderID,
model.like
)
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity, error.toString())
}
})
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mMainActivity, error.toString())
}
})
holder.sMessage.setOnClickListener {
startActivity(
Intent(this@MainActivity, PostMessageActivity::class.java)
.putExtra("listID", lisResUid)
)
}
holder.sShare.setOnClickListener { holder.shareUri(model.videoUrl) }
holder.readyPlayer(model.videoUrl, model.videoName)
holder.bindVisibility()
}
}
adapterFire.startListening()
val snapHelper = LinearSnapHelper()
binding.videoRecycler.onFlingListener = null
binding.videoRecycler.clearOnScrollListeners()
snapHelper.attachToRecyclerView(binding.videoRecycler)
binding.videoRecycler.adapter = adapterFire
}
