Simple Messaging App Part 2: Adding the Account System - Huawei Developers

In the previous part we have prepared our backend to receive messages and deliver them to the proper user. As many apps, our application needs an uthentication system, so only verified users will be able to use the app and appliy for a nickname. In this part, we will add the account system to our Messenger app.
Adding Dependencies
We will use AGC Auth Service to allow the users sing in with Huawei ID, Mail and Phone Number, let's add the AGC Auth Service and Account kit Dependencies
Code:
implementation 'com.huawei.agconnect:agconnect-auth:1.4.1.300'
implementation 'com.huawei.hms:hwid:5.0.1.301'
App Startup
Auth Service will keep the user logged in the app until the user perform a log out operation. We must verify if there is a logged user upon each app startup. Also we must check if the user has choosen a nickname to be identified by other users.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
The Auth Service has an API to quickly check if there is a user already signed in the application.
Code:
val user = AGConnectAuth.getInstance().currentUser
If the user is null, we must display the sign in page, by other way, we must check if the user has choosen a nickname.
Code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val user = AGConnectAuth.getInstance().currentUser
if(user!=null)checkNickName(user.uid)
else redirectToLogin()
}
}
Building the login activity
For now, this page will only contain 2 buttons, one for sign in with Huawei, and other to use a mobile phone or email.
Note:The code will be designed for supporting phone number, but full phone number support will be added later.
Code:
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<data class="LoginBinding">
<variable
name="loginVM"
type="com.hms.demo.messengerapp.LoginViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".LoginActivity"
android:padding="16dp">
<com.huawei.hms.support.hwid.ui.HuaweiIdAuthButton
android:id="@+id/huaweiIdAuthButton"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:onClick="@{()->loginVM.huaweiIdLogin(context)}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/button" />
<ImageView
android:id="@+id/imageView"
android:layout_width="232dp"
android:layout_height="439dp"
android:layout_marginTop="20dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView"
app:srcCompat="@drawable/online_worlds" />
<Button
android:id="@+id/button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:text="@string/mailPhoneLogin"
android:onClick="@{()->loginVM.mailPhoneLogin()}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/imageView" />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/loginTitle"
android:textAlignment="center"
android:textSize="28sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Now we need to add a ViewModel which will listen the buttons
Code:
class LoginViewModel : ViewModel() {
companion object{
const val HW_ID=100
const val MAIL_PHONE=200
}
val navigator:ViewNavigator?=null
public fun huaweiIdLogin(context: Context){
val authParams = HuaweiIdAuthParamsHelper(HuaweiIdAuthParams.DEFAULT_AUTH_REQUEST_PARAM)
.setAuthorizationCode()
.createParams()
val service = HuaweiIdAuthManager.getService(context, authParams)
navigator?.navigateForResult(service.signInIntent, HW_ID)
}
public fun mailPhoneLogin(context: Context){
val intent=Intent(context, MailPhoneLoginActivity::class.java)
navigator?.navigateForResult(intent,MAIL_PHONE)
}
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?){
when(requestCode){
HW_ID -> handleHuaweiID(data)
MAIL_PHONE -> handleMailPhoneUser(resultCode)
}
}
private fun handleHuaweiID(data: Intent?) {
val authHuaweiIdTask = HuaweiIdAuthManager.parseAuthResultFromIntent(data)
if (authHuaweiIdTask.isSuccessful) {
val huaweiAccount = authHuaweiIdTask.result
val credential = HwIdAuthProvider.credentialWithToken(huaweiAccount.accessToken)
AGConnectAuth.getInstance().signIn(credential).addOnSuccessListener{
askForNickName()
}
}
}
private fun handleMailPhoneUser(resultCode: Int) {
if(resultCode==200){
askForNickName()
}
}
private fun askForNickName() {
}
interface ViewNavigator{
fun navigateForResult(intent: Intent,requestCode:Int)
}
}
The login activity will connect the layout with the viewModel
Code:
class LoginActivity : AppCompatActivity(),LoginViewModel.ViewNavigator{
lateinit var viewModel:LoginViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding=LoginBinding.inflate(layoutInflater)
setContentView(binding.root)
viewModel =ViewModelProviders.of(this).get(LoginViewModel::class.java)
binding.loginVM=viewModel
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
viewModel.onActivityResult(requestCode,resultCode,data)
}
override fun navigateForResult(intent: Intent, requestCode: Int) {
startActivityForResult(intent,requestCode)
}
}
MailPhoneLoginActivity
The MailPhoneLoginActivity will request and wait for a verification code for mail or phone and if the verification is complete, will set a result with a result code 200.
The layout code is as follows
Code:
<?xml version="1.0" encoding="utf-8"?>
<layout
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">
<data class="MailPhoneBinding">
<variable
name="viewModel"
type="com.hms.demo.messengerapp.MailPhoneVM" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MailPhoneLoginActivity"
android:padding="16dp">
<EditText
android:id="@+id/inputCode"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="24dp"
android:ems="10"
android:hint="@string/verificationMessage2"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.497"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView3" />
<TextView
android:id="@+id/textView2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="28dp"
android:text="@string/verificationMessage"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView4" />
<TextView
android:id="@+id/textView4"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/verificationTitle"
android:textAlignment="center"
android:textSize="28sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="@+id/button2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/verify"
android:onClick="@{()->viewModel.verifyCode(inputCode.text)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<EditText
android:id="@+id/inputMailPhone"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="76dp"
android:ems="10"
android:hint="@string/mailPhone"
android:inputType="textPersonName"
app:layout_constraintEnd_toStartOf="@+id/button3"
app:layout_constraintHorizontal_bias="0.792"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<Button
android:id="@+id/button3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="72dp"
android:layout_marginEnd="44dp"
android:onClick="@{()->viewModel.getCode(inputMailPhone.text)}"
android:text="@string/getCode"
android:textAllCaps="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/textView2" />
<TextView
android:id="@+id/textView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="64dp"
android:text="@string/verificationMessage1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/inputMailPhone" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Once the code is entered, th user can press the verify button to create an AGCUser on the ViewModel
Code:
class MailPhoneVM : ViewModel() {
companion object{
val mailRegex = Regex("^[\\w-\\.][email protected]([\\w-]+\\.)+[\\w-]{2,4}\$")
val phoneRegex=Regex("^\\+[1-9]{1}[0-9]{3,14}\$\n")
const val ACCOUNT_TYPE="account_type"
const val VALUE="value"
const val MAIL="mail"
const val PHONE="phone"
}
var navigator:MailPhoneNavigator?=null
private var dataType=MAIL
private var value=""
fun getCode(editable: Editable){
if (mailRegex.matches(editable.toString())) {
//is mail
getMailCode(editable.toString())
}
else if (phoneRegex.matches(editable.toString())){
//is phone
}
}
private fun getMailCode(mail: String) {
dataType= MAIL
value=mail
val settings = VerifyCodeSettings.newBuilder()
.action(VerifyCodeSettings.ACTION_REGISTER_LOGIN) //ACTION_REGISTER_LOGIN/ACTION_RESET_PASSWORD
.sendInterval(30) // Minimum sending interval, ranging from 30s to 120s.
.build()
val task: Task<VerifyCodeResult> = EmailAuthProvider.requestVerifyCode(
mail,
settings
)
task.addOnSuccessListener {
//The verification code application is successful.
}
.addOnFailureListener {
Log.e("EmailAuth", it.toString())
}
}
private fun getMobileCode(phone:String){
dataType= PHONE
value=phone
}
fun verifyCode(editable: Editable){
when(dataType){
PHONE -> verifyPhone(value,editable.toString())
MAIL -> verifyMail(value,editable.toString())
}
}
private fun verifyMail(email: String,code:String) {
val emailUser = EmailUser.Builder()
.setEmail(email)
.setVerifyCode(code)
.build()
AGConnectAuth.getInstance().createUser(emailUser)
.addOnSuccessListener{
navigator?.sendResult(200)
}
.addOnFailureListener{
Log.e("AuthSevice","Email Sign in failed $it")
}
}
private fun verifyPhone(phone: String,code:String) {
}
interface MailPhoneNavigator{
fun sendResult(result:Int)
}
}
Finally, we must connect everything on the activity
Code:
class MailPhoneLoginActivity : AppCompatActivity(), MailPhoneVM.MailPhoneNavigator {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding = MailPhoneBinding.inflate(layoutInflater)
setContentView(binding.root)
val vm=ViewModelProviders.of(this).get(MailPhoneVM::class.java)
vm.navigator=this
binding.viewModel=vm
}
override fun sendResult(result: Int) {
setResult(result)
finish()
}
}
Conclusion
In this part we have integrated the Huawei Auth Service to allow users quickly create an account in our app. Stay tunned for the next part.

How much we need to wait for OTP ?

Related

Exo Player — HMS Video Kit Comparison by building a flavored Android Application . Part II

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Building Activities for this Project
Before passing to the specialized flavor dimension let’s build:
Main Activity (Part-II)
Record Activity (Part-III)
Single Player (Fullscreen) Activity (Part-IV)
Profile Activity & Bonus Content (Part-V)
* Main Activity
Let’s create our menu items:
XML:
<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:
XML:
<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:
XML:
<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:
XML:
<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:
Code:
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:
Code:
class IMain {
interface ViewMain{
fun setupDialog(context: Context,type:Int)
fun setupRecycler()
fun checkItems()
}
}
Then for its presenter:
Code:
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:
Code:
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(…)
Code:
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:
Code:
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:
Code:
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:
Code:
override fun onStart() {
super.onStart()
setupRecycler()
checkItems()
}
onCreate():
Code:
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([email protected])
}
/* Record */
1 -> {
setupDialog([email protected], R.style.BlurTheme)
}
/* Profile */
2 -> {
presenter.gotoProfile([email protected])
}
}
}
override fun onTabReselected(index: Int, tab: AnimatedBottomBar.Tab) {
super.onTabReselected(index, tab)
when (index) {
0 -> {
presenter.restart([email protected])
}
1 -> {
setupDialog([email protected], R.style.BlurTheme)
}
2 -> {
presenter.gotoProfile([email protected])
}
}
}
})
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.
Code:
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([email protected], 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
The final preview of MainActivity
------------------------------
END OF PART - II

Exo Player — HMS Video Kit Comparison by building a flavored Android Application . Part V

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Building Activities for this Project
Before passing to the specialized flavor dimension let’s build:
Main Activity (Part-II)
Record Activity (Part-III)
Single Player (Fullscreen) Activity (Part-IV)
Profile Activity & Bonus Content (Part-V)
* Profile Activity
On this part, we won’t be using any of the Exo Player or Video Kit. Our job here is to give the user to control his/her/them videos shareability, deleteability, or ability to watch those videos on the larger player under previously created Single Player either with Exo Player or with Video Kit.
So in this part, we’ll be using 3 items on a row kinda Grid layout manager of recycler view with some animations.
Let’s start by creating single items for this grid:
XML:
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="@dimen/grid_video"
android:layout_height="@dimen/grid_video"
android:layout_margin="3dp"
android:clickable="true"
android:focusable="true"
android:foreground="@drawable/round_ripple"
app:cardCornerRadius="10dp">
<!-- Front View -->
<RelativeLayout
android:id="@+id/vid_front"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:background="@drawable/ic_rectangle_orange"
android:visibility="visible">
<com.klinker.android.simple_videoview.SimpleVideoView
android:id="@+id/grid_video"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:soundEffectsEnabled="false"
app:loop="true"
app:muted="true"
app:showSpinner="true"
app:stopSystemAudio="false" />
<ImageButton
android:id="@+id/pp_button"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/transparent"
android:contentDescription="@string/play_pause_button"
android:src="@drawable/ic_pause"
android:visibility="gone" />
<ImageView
android:layout_width="18dp"
android:layout_height="18dp"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:layout_marginEnd="0dp"
android:layout_marginBottom="0dp"
android:contentDescription="@string/image"
android:padding="3dp"
android:src="@drawable/ic_image" />
</RelativeLayout>
<!-- Back View -->
<LinearLayout
android:id="@+id/vid_back"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/ic_rectangle_orange"
android:orientation="vertical"
android:padding="5dp"
android:visibility="gone"
android:weightSum="4">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/big_player"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:gravity="center_vertical"
android:maxEms="1"
android:maxLines="1"
android:text="@string/fullscreen"
android:textColor="@color/white"
app:drawableStartCompat="@drawable/ic_fullscreen" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="@color/purple_700" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/share"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:gravity="center_vertical"
android:maxEms="1"
android:maxLines="1"
android:text="@string/hide"
android:textColor="@color/white"
app:drawableStartCompat="@drawable/ic_hide" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="@color/purple_700" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/delete"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:gravity="center_vertical"
android:maxEms="1"
android:maxLines="1"
android:text="@string/delete"
android:textColor="@color/white"
app:drawableStartCompat="@drawable/ic_delete" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="@color/purple_700" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/cancel"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:clickable="true"
android:ellipsize="end"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:gravity="center_vertical"
android:maxEms="1"
android:maxLines="1"
android:text="@string/cancel"
android:textColor="@color/white"
app:drawableStartCompat="@drawable/ic_cancel" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
Now to our profile activities layout:
XML:
<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="@drawable/colour_bg"
android:padding="10dp"
tools:context=".ui.activities.ProfileActivity">
<!-- Banner -->
<LinearLayout
android:id="@+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/profile_image"
android:layout_width="0dp"
android:layout_height="@dimen/profile_image"
android:layout_weight=".4"
app:civ_border_color="@color/white"
app:civ_border_width="1dp"
tools:src="@drawable/ic_person" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="@dimen/profile_image"
android:layout_weight="1.5"
android:orientation="vertical"
android:weightSum="1">
<TextView
android:id="@+id/profile_total_video"
style="@style/TextAppearance.AppCompat.Large"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight=".5"
android:fontFamily="@font/phenomena_regular"
android:gravity="center"
android:textColor="@color/white"
tools:text="0" />
<TextView
style="@style/TextAppearance.AppCompat.Large"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight=".5"
android:fontFamily="@font/bicubik"
android:gravity="center"
android:text="@string/videos"
android:textColor="@color/white" />
</LinearLayout>
<ImageButton
android:id="@+id/prof_settings"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center"
android:layout_weight=".1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/settings"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:src="@drawable/ic_setting" />
</LinearLayout>
<View
android:id="@+id/view2"
android:layout_width="match_parent"
android:layout_height="2dp"
android:layout_marginTop="10dp"
android:layout_marginBottom="10dp"
android:background="@color/darker_light_fuchsia"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout2" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/grid_recycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/view2"
tools:listitem="@layout/grid_video_item" />
</androidx.constraintlayout.widget.ConstraintLayout>
View Holder for our grid view:
Code:
class ProfileGridViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val parent: View = itemView
private val simpleVideo: SimpleVideoView
val ppButton: ImageView
val vidBack: LinearLayout
val vidFront: RelativeLayout
val bigPlayer: TextView
val cancel: TextView
val delete: TextView
val share: TextView
fun bindImage(videoUrl: String) {
simpleVideo.start(Uri.parse(videoUrl))
}
fun startStop() {
if (simpleVideo.isPlaying) {
simpleVideo.pause()
ppButton.visibility = View.VISIBLE
ppButton.setImageResource(R.drawable.ic_pause)
val timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
ppButton.expandView()
}
}, 3000)
} else {
simpleVideo.play()
ppButton.visibility = View.VISIBLE
ppButton.setImageResource(R.drawable.ic_play)
val timer = Timer()
timer.schedule(object : TimerTask() {
override fun run() {
ppButton.expandView()
}
}, 3000)
}
}
fun startSinglePlayer(videoUrl: String) {
bigPlayer.setOnClickListener {
parent.context.startActivity(
Intent(parent.context, SinglePlayerActivity::class.java)
.putExtra("url", videoUrl)
.putExtra("type", 0)
)
}
}
init {
simpleVideo = parent.findViewById(R.id.grid_video)
ppButton = parent.findViewById(R.id.pp_button)
vidBack = parent.findViewById(R.id.vid_back)
vidFront = parent.findViewById(R.id.vid_front)
bigPlayer = parent.findViewById(R.id.big_player)
cancel = parent.findViewById(R.id.cancel)
delete = parent.findViewById(R.id.delete)
share = parent.findViewById(R.id.share)
}
}
Then interface to our profile:
Code:
class IProfile {
interface ViewProfile{
fun onViewsCreate()
fun populateViews()
fun setupRecycler()
}
}
Finally on our activity:
Code:
class ProfileActivity : AppCompatActivity(), IProfile.ViewProfile {
private lateinit var binding: ActivityProfileBinding
private val profilePresenter: ProfilePresenter by lazy {
ProfilePresenter()
}
…
}
Let’s start by filling overridden methods and custom ones:
onViewsCreate()
Code:
override fun onViewsCreate() {
binding.gridRecycler.layoutManager = GridLayoutManager(this, 3)
binding.gridRecycler.setHasFixedSize(true)
}
PopulateViews()
Code:
fun populateViews() {
Constants.fUserInfoDB.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val img = snapshot.child("photoUrl").value.toString()
val name = snapshot.child("nameSurname").value.toString()
Picasso.get().load(img).centerCrop().fit().into(binding.profileImage)
supportActionBar?.title = getString(R.string.welcome, name)
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mProfileActivity, error.message)
}
})
Constants.fFeedRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
val pTotal = NumberConvertor.prettyCount(snapshot.children.count())
binding.profileTotalVideo.text = pTotal
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mProfileActivity, error.toString())
}
})
}
Setting up recycler:
Code:
override fun setupRecycler() {
val options = FirebaseRecyclerOptions.Builder<DataClass.ProfileVideoDataClass>()
.setQuery(Constants.fFeedRef, DataClass.ProfileVideoDataClass::class.java).build()
val adapterFire = object :
FirebaseRecyclerAdapter<DataClass.ProfileVideoDataClass, ProfileGridViewHolder>(options) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): ProfileGridViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.grid_video_item, parent, false)
return ProfileGridViewHolder(view)
}
override fun onBindViewHolder(
holder: ProfileGridViewHolder,
position: Int,
model: DataClass.ProfileVideoDataClass
) {
val lisResUid = getRef(position).key
val scale = [email protected].density
holder.vidFront.cameraDistance = 8000 * scale
holder.vidBack.cameraDistance = 8000 * scale
val frontAnim =
AnimatorInflater.loadAnimator(
[email protected],
R.animator.front_animator
) as AnimatorSet
val backAnim = AnimatorInflater.loadAnimator(
[email protected],
R.animator.back_animator
) as AnimatorSet
val dbRef = FirebaseDbHelper.getVideoFeedItem(AppUser.getUserId(), lisResUid!!)
dbRef.addValueEventListener(object : ValueEventListener {
override fun onDataChange(snapshot: DataSnapshot) {
mShared = snapshot.child("shareStat").value.toString()
when (mShared) {
"1" -> {
holder.share.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_hide,
0,
0,
0
)
holder.share.text = getString(R.string.hide)
holder.share.setOnClickListener {
profilePresenter.moveShowToHide([email protected], lisResUid)
}
}
"0" -> {
holder.share.setCompoundDrawablesWithIntrinsicBounds(
R.drawable.ic_show,
0,
0,
0
)
holder.share.text = getString(R.string.share)
holder.share.setOnClickListener {
profilePresenter.moveHideToShow([email protected], lisResUid)
}
}
}
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mProfileActivity, error.message)
}
})
holder.startSinglePlayer(model.videoUrl)
holder.vidFront.setOnClickListener {
frontAnim.setTarget(holder.vidFront)
backAnim.setTarget(holder.vidBack)
frontAnim.start()
backAnim.start()
holder.vidFront.visibility = View.GONE
holder.vidBack.visibility = View.VISIBLE
holder.cancel.setOnClickListener {
frontAnim.setTarget(holder.vidBack)
backAnim.setTarget(holder.vidFront)
frontAnim.start()
backAnim.start()
holder.vidFront.visibility = View.VISIBLE
holder.vidBack.visibility = View.GONE
}
}
holder.bindImage(model.videoUrl)
//holder.simpleVideo.setOnClickListener { holder.startStop() }
}
}
adapterFire.startListening()
binding.gridRecycler.adapter = adapterFire
}
OnCreate:
Code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityProfileBinding.inflate(layoutInflater)
setContentView(binding.root)
Slidr.attach(this)
onViewsCreate()
populateViews()
}
Final result for activity
------------------------------
END OF PART - V

Exo Player — HMS Video Kit Comparison by building a flavored Android Application . Part VI

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Bonus Content — Sending comment messages to videos Post Message Activity
On this bonus content will try to comment on videos on double-layered message model, like posting comments on videos and also posting comments on posted comments. Post-commentception
Let’s begin by creating a single item.
PS: I know it might look bright and unreadable on a white background but we’ll use it on darker background
XML:
<RelativeLayout 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="wrap_content"
android:orientation="vertical"
android:padding="10dp">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottie_post_lovely"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_alignBottom="@+id/single_bottom_text"
android:layout_alignParentTop="true"
android:layout_marginBottom="0dp"
android:background="@color/transparent"
android:visibility="gone"
android:elevation="11dp"
app:lottie_autoPlay="false"
app:lottie_rawRes="@raw/downwards_lovely_lottie" />
<LinearLayout
android:id="@+id/single_bottom_text_sender_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clickable="true"
android:focusable="true"
android:foreground="?android:selectableItemBackground"
android:orientation="horizontal">
<de.hdodenhof.circleimageview.CircleImageView
android:id="@+id/single_bottom_text_sender_circle"
android:layout_width="48dp"
android:layout_height="48dp"
android:background="@drawable/circle_bg"
android:padding="3dp"
tools:src="@drawable/ic_person" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:orientation="vertical">
<TextView
android:id="@+id/single_bottom_text_sender_name"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:fontFamily="serif"
tools:text="@string/name"
android:textColor="@color/amberDark"
android:textStyle="bold" />
<TextView
android:id="@+id/single_bottom_text_get_time_ago"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginStart="10dp"
android:layout_marginTop="8dp"
android:gravity="end"
android:text="@string/timestamp"
android:textColor="@color/amberLight"
android:textSize="10sp" />
</LinearLayout>
</LinearLayout>
<TextView
android:id="@+id/single_bottom_text"
style="@style/TextAppearance.AppCompat.Small"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/single_bottom_text_sender_layout"
android:layout_marginStart="58dp"
android:layout_marginTop="10dp"
android:fontFamily="@font/alegreya_sans_medium"
tools:text="@string/dummyText"
android:textColor="@color/amber" />
<LinearLayout
android:id="@+id/reply_lyt"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/single_bottom_text"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:weightSum="1">
<TextView
android:id="@+id/single_bottom_reply"
style="@style/TextAppearance.AppCompat.Medium"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_weight=".9"
android:clickable="true"
android:focusable="true"
android:fontFamily="sans-serif-black"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:text="@string/reply"
android:textColor="@color/amberDark"
android:textSize="16sp" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/single_bottom_lovely"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".1"
android:clickable="true"
android:contentDescription="@string/lovely"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:gravity="end"
android:text="@string/zero"
android:textColor="@color/amberLight"
app:drawableEndCompat="@drawable/ic_heart" />
</LinearLayout>
<LinearLayout
android:id="@+id/single_bottom_view_reply"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/reply_lyt"
android:layout_marginTop="10dp"
android:clickable="true"
android:focusable="true"
android:foreground="?attr/selectableItemBackgroundBorderless"
android:orientation="horizontal">
<View
android:layout_width="80dp"
android:layout_height="1dp"
android:layout_gravity="center_vertical"
android:background="@color/amberDark" />
<TextView
style="@style/TextAppearance.AppCompat.Small"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:text="@string/view_replies"
android:textColor="@color/amberDark"
android:textStyle="bold" />
<TextView
android:id="@+id/totalUnderTV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:text="@string/zero"
android:textColor="@color/amberLight"/>
</LinearLayout>
</RelativeLayout>
Now let’s look at its view holder
Code:
class PostMessageViewHolder(itemView:View): RecyclerView.ViewHolder(itemView) {
val parent = itemView
val mCommenterLayout : LinearLayout
val mViewReply : LinearLayout
private var mCommenterCircle : CircleImageView
private var mCommenterName : TextView
private var mCommentTime : TextView
private var mComment : TextView
var mLottieAnimationView : LottieAnimationView
private var totalUnder : TextView
val mReply : TextView
val mLovely : TextView
init {
mCommenterLayout = parent.findViewById(R.id.single_bottom_text_sender_layout)
mViewReply = parent.findViewById(R.id.single_bottom_view_reply)
totalUnder = parent.findViewById(R.id.totalUnderTV)
mCommenterCircle = parent.findViewById(R.id.single_bottom_text_sender_circle)
mCommenterName = parent.findViewById(R.id.single_bottom_text_sender_name)
mCommentTime = parent.findViewById(R.id.single_bottom_text_get_time_ago)
mComment = parent.findViewById(R.id.single_bottom_text)
mLottieAnimationView = parent.findViewById(R.id.lottie_post_lovely)
mReply = parent.findViewById(R.id.single_bottom_reply)
mLovely = parent.findViewById(R.id.single_bottom_lovely)
}
fun bindTotalUnder(string: String){
val pTotal = NumberConvertor.prettyCount(string.toLong())
totalUnder.visibility = View.VISIBLE
totalUnder.text = pTotal
}
fun bindComments(circleImage : String,
name : String,
time : String,
item : String,
lovely : String){
Picasso.get().load(circleImage).centerCrop().fit().into(mCommenterCircle)
mCommenterName.text = name
mCommentTime.text = time
mComment.text = item
val pLovely = NumberConvertor.prettyCount(lovely.toLong())
mLovely.text = pLovely
}
}
Now back on our activity layout
XML:
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:background="@drawable/colour_bg"
tools:context=".ui.activities.PostMessageActivity">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/message_list"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintBottom_toTopOf="@+id/linearLayout3"
app:layout_constraintEnd_toEndOf="parent"
tools:listitem="@layout/post_message_item"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/linearLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:weightSum="2"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<com.google.android.material.card.MaterialCardView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:layout_weight="1.8"
app:cardBackgroundColor="@color/transparent"
app:cardCornerRadius="15dp">
<EditText
android:id="@+id/send_input"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/colour_bg_sender"
android:fontFamily="@font/roboto"
android:hint="@string/type_a_message"
android:importantForAutofill="no"
android:inputType="text"
android:padding="10dp"
android:textColor="@color/white"
android:textColorHint="@color/gray_fade" />
</com.google.android.material.card.MaterialCardView>
<ImageButton
android:id="@+id/send_button"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_gravity="center_vertical"
android:layout_weight=".2"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/send"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:src="@drawable/ic_send" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
The interface of Post Message Activity
Code:
class IPostMessage {
interface ViewPostMessage {
fun initViews()
fun initDB()
fun sendComments()
}
interface PresenterPostMessage {
fun onViewsCreate()
fun setupRecycler(
context: Context,
currentID: String,
query: Query,
mRecycler: RecyclerView,
postID: String,
)
}
}
Presenter of Post Message Activity
Code:
class PostMessagePresenter constructor(private val contract: IPostMessage.ViewPostMessage) :
IPostMessage.PresenterPostMessage {
override fun onViewsCreate() {
contract.initViews()
contract.initDB()
}
override fun setupRecycler(
context: Context,
currentID: String,
query: Query,
mRecycler: RecyclerView,
postID: String
) {
val mp = MediaPlayer.create(context, R.raw.heart_fall1)
val options = FirebaseRecyclerOptions.Builder<DataClass.PostMessageDataClass>()
.setQuery(query, DataClass.PostMessageDataClass::class.java).build()
val adapterFire = object :
FirebaseRecyclerAdapter<DataClass.PostMessageDataClass, PostMessageViewHolder>(options) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): PostMessageViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.post_message_item, parent, false)
return PostMessageViewHolder(view)
}
override fun onBindViewHolder(
holder: PostMessageViewHolder,
position: Int,
model: DataClass.PostMessageDataClass
) {
val lisResId = getRef(position).key
if (model.commenter_ID == currentID)
holder.bindComments(
model.commenter_image,
model.commenter_name + " (" + context.getString(R.string.me) + ")",
model.time,
model.comment,
model.comment_lovely
)
else
holder.bindComments(
model.commenter_image,
model.commenter_name,
model.time,
model.comment,
model.comment_lovely
)
var lovely = model.comment_lovely.toInt()
val dbRef = FirebaseDbHelper.getPostMessageRef(postID).child(lisResId!!)
holder.mLovely.setOnClickListener {
mp.start()
lovely++
dbRef!!.child("comment_lovely").setValue(lovely.toString())
holder.mLottieAnimationView.visibility = View.VISIBLE
holder.mLottieAnimationView.bringToFront()
holder.mLottieAnimationView.playAnimation()
showLogDebug(Constants.mPostMessagePresenter, "KEy: $lisResId")
}
}
}
adapterFire.startListening()
mRecycler.adapter=adapterFire
}
}
The activity of Post Message Activity
Code:
class PostMessageActivity : AppCompatActivity(), IPostMessage.ViewPostMessage {
private lateinit var binding: ActivityPostMessageBinding
private val presenter:PostMessagePresenter by lazy {
PostMessagePresenter(this)
}
private val currentID = AppUser.getUserId()
private lateinit var postMessageDBRef: DatabaseReference
private lateinit var query: Query
private lateinit var postUploadsRef: DatabaseReference
private lateinit var mLayoutManager: LinearLayoutManager
private lateinit var mp: MediaPlayer
private lateinit var commenterName: String
private lateinit var commenterImg: String
private lateinit var commentGroupID: String
private lateinit var commentID: String
private var listID :String?=""
...
}
Let’s start by filling overridden methods and custom ones:
initViews()
Code:
override fun initViews() {
listID = intent.getStringExtra("listID")
mp = MediaPlayer.create(this, R.raw.message_send)
mLayoutManager = LinearLayoutManager(this)
mLayoutManager.reverseLayout =true
mLayoutManager.stackFromEnd = true
binding.messageList.layoutManager = mLayoutManager
}
initDB()
Code:
override fun initDB(){
postUploadsRef = FirebaseDbHelper.getShareItem(listID!!)
postMessageDBRef = FirebaseDbHelper.getPostMessageRef(listID!!)
query =postMessageDBRef.orderByChild("comment_lovely")
presenter.setupRecycler(this,currentID,query,binding.messageList,listID!!)
FirebaseDbHelper.getUserInfo(currentID).addValueEventListener(object :ValueEventListener{
override fun onDataChange(snapshot: DataSnapshot) {
commenterName = snapshot.child("nameSurname").value.toString()
commenterImg = snapshot.child("photoUrl").value.toString()
}
override fun onCancelled(error: DatabaseError) {
showLogError(Constants.mPostMessageActivity,error.toString())
}
})
}
sendComments()
Code:
override fun sendComments() {
val comments = binding.sendInput.text.toString()
if (comments!=""){
mp.start()
val commentPush = FirebaseDbHelper.rootRef()
.child("PostMessage")
.child(listID!!)
.child(currentID).push()
commentGroupID = commentPush.key.toString()
val cmPsh = FirebaseDbHelper.rootRef()
.child("PostMessage")
.child(listID!!).push()
commentID = cmPsh.key.toString()
val commentsRef = "PostMessage/$listID/$commentID"
val commentUnderRef = "PostMessageUnder/$listID/$commentID"
val commentMap: MutableMap<String,String> = HashMap()
commentMap["comment"] = comments
commentMap["time"] = DateFormat.getDateTimeInstance().format(Date())
commentMap["commenter_name"] = commenterName
commentMap["commenter_image"] = commenterImg
commentMap["commenter_ID"] = currentID
commentMap["type"] = "text"
commentMap["comment_lovely"] = "0"
val mapGroup:MutableMap<String,Any> = HashMap()
mapGroup[commentUnderRef] = commentMap
FirebaseDbHelper.rootRef().updateChildren(mapGroup)
val mapContent: MutableMap<String,Any> = HashMap()
mapContent[commentsRef] = commentMap
FirebaseDbHelper.rootRef().updateChildren(mapContent)
}
}
onCreate()
Code:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPostMessageBinding.inflate(layoutInflater)
setContentView(binding.root)
supportActionBar?.title=getString(R.string.thread)
Slidr.attach(this)
presenter.onViewsCreate()
binding.sendButton.setOnClickListener {
sendComments()
binding.sendInput.setText("")
}
}
The final result for our posting comments for individual videos.
------------------------------
END OF PART - VI

Exo Player — HMS Video Kit Comparison by building a flavored Android Application . Part VIII

{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Note: Bear in mind that any class or layout file names we create under flavor dimensions that we’ll use under our main must match on both flavor dimensions! As an example “HmsGmsVideoHelper.kt” this class name must exist on both flavors if we are to use it on under our main
HMS Video Kit
Supported Formats
HUAWEI Video Kit provides video playback in this version and will support video editing and video hosting in later versions, helping you quickly build desired video features to deliver a superb video experience to your app users.
You can integrate the Video Kit WisePlayer SDK into your app so that it can play streaming media from a third-party video address. Streaming media must be in 3GP, MP4, or TS format and comply with HTTP/HTTPS, HLS, or DASH. Currently, it cannot play local video.
For example, you want to promote your tool app using a promotional video in it, and have hosted the video on a third-party cloud platform. In this case, you can directly call the playback API of the Video Kit WisePlayer SDK to play the video online.
Implementing HMS Video Kit
In this example will see 2 implementations. One with a big player, the other with a classical recycler view approach.
Refer to app creation guide on Huawei's offical site first create an HMS application to create an agconnect-services.json file
Let us first start by creating our interfaces under hms flavor and we’ll continue building everything in this section under hms flavor. Keep in mind! :
Firstly we have to initialize WisePlay Services. We need to do that under hms flavor because bear in mind that we have separated its implementation under the notation of “hmsImplementation”. For to initialize it we have to create a new application class.
WisePlayer Init, This object is hms specific so we don’t have to create its mirror object on gms flavor.
Code:
object WisePlayerInit {
lateinit var wisePlayerFactory: WisePlayerFactory
fun initialize(context: Context) {
// TODO Initializing of Wise Player Factory
val factoryOptions = WisePlayerFactoryOptions.Builder().setDeviceId("xxx").build()
// In the multi-process scenario, the onCreate method in Application is called multiple times.
// The app needs to call the WisePlayerFactory.initFactory() API in the onCreate method of the app process (named "app package name")
// and WisePlayer process (named "app package name:player").
WisePlayerFactory.initFactory(context, factoryOptions, object : InitFactoryCallback {
override fun onSuccess(factory: WisePlayerFactory) {
showLogInfo("WisePlayerInit","WisePlayerInit Success")
wisePlayerFactory = factory
}
override fun onFailure(errorCode: Int, msg: String) {
showLogError("WisePlayerInit", "onFailure: $errorCode - $msg")
}
})
}
fun createPlayer(): WisePlayer? {
//TODO Initializing of Wise Player Instance
return if (::wisePlayerFactory.isInitialized) {
wisePlayerFactory.createWisePlayer()
} else {
null
}
}
}
Application class, This application class is necessary for hms only, so we don’t have to create its mirror application class on gms flavor.
Code:
class ExoVideoApp : Application() {
override fun onCreate() {
super.onCreate()
setApp(this)
WisePlayerInit.initialize(this)
}
companion object {
var instance: ExoVideoApp? = null
private set
val context: Context
get() = instance!!.applicationContext
@Synchronized
private fun setApp(app: ExoVideoApp) {
instance = app
}
}
}
Also, this manifest file is hms specific. Once we run our application it will merge itself with our original manifest file. Now, let’s call it in the manifest.
XML:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.onurcan.exovideoreference">
<uses-permission android:name="com.huawei.permission.SECURITY_DIAGNOSE" />
<application tools:ignore="AllowBackup"
android:name="application.ExoVideoApp">
</application>
</manifest>
Lets define our interfaces to use within the Video Kits lifecycle.
OnInteract
Code:
interface OnInteract {
fun bindVisibility()
fun initUI(type: Int)
fun configureContentV()
fun configureControlV()
fun restartPlayer(videoUrl: String)
fun changePlayState()
fun setPauseView()
fun setPlayView()
fun readyPlayer(videoUrl: String, string: String)
}
OnPlayWindowListener
Code:
interface OnPlayWindowListener : SurfaceHolder.Callback, TextureView.SurfaceTextureListener {
}
OnWisePlayerListener
Code:
interface OnWisePlayerListener: SeekBar.OnSeekBarChangeListener,
WisePlayer.ErrorListener, WisePlayer.EventListener, WisePlayer.ResolutionUpdatedListener,
WisePlayer.LoadingListener, WisePlayer.SeekEndListener, WisePlayer.PlayEndListener,WisePlayer.ReadyListener
{
}
Now let look at the video layout
XML:
<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">
<FrameLayout
android:id="@+id/frameLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_percent="0.9">
<ImageButton
android:id="@+id/info_panel_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginStart="10dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="10dp"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/info"
android:elevation="4dp"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:src="@drawable/ic_info_circle" />
<SurfaceView
android:id="@+id/surfaceView"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottieView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:elevation="2dp"
app:lottie_autoPlay="true"
app:lottie_loop="true"
app:lottie_rawRes="@raw/lottie_loading2" />
</FrameLayout>
<!-- UI Controller -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:background="@drawable/content_control_background"
android:elevation="3dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<ImageButton
android:id="@+id/extend_control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/arrow"
android:focusable="true"
android:foreground="@drawable/round_ripple"
android:src="@drawable/ic_arrow_drop_down"
android:tint="@color/white" />
<LinearLayout
android:id="@+id/content_social"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp"
android:weightSum="3">
<TextView
android:id="@+id/lovely"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/transparent"
android:clickable="true"
android:focusable="true"
android:fontFamily="@font/bitter_italic"
android:foreground="@drawable/round_selector"
android:gravity="center_horizontal"
android:text="@string/lovely"
android:textColor="@color/textColor_night"
app:drawableTopCompat="@drawable/ic_heart" />
<TextView
android:id="@+id/comment"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/transparent"
android:clickable="true"
android:focusable="true"
android:fontFamily="@font/bitter_italic"
android:foreground="@drawable/round_selector"
android:gravity="center_horizontal"
android:text="@string/comment"
android:textColor="@color/textColor_night"
app:drawableTopCompat="@drawable/ic_message" />
<TextView
android:id="@+id/share"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/transparent"
android:clickable="true"
android:focusable="true"
android:fontFamily="@font/bitter_italic"
android:foreground="@drawable/round_selector"
android:gravity="center_horizontal"
android:text="@string/share"
android:textColor="@color/textColor_night"
app:drawableTopCompat="@drawable/ic_share" />
</LinearLayout>
<LinearLayout
android:id="@+id/content_info"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:visibility="visible"
android:weightSum="1"
tools:visibility="visible">
<de.hdodenhof.circleimageview.CircleImageView
android:layout_width="0dp"
android:layout_height="48dp"
android:layout_weight=".2"
android:src="@drawable/video_stock" />
<TextView
android:id="@+id/video_name"
style="@style/TextAppearance.AppCompat.Small"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:layout_weight=".8"
android:ellipsize="end"
android:fontFamily="@font/bitter_italic"
android:maxEms="3"
android:maxLines="3"
android:textColor="@color/white"
android:textSize="12sp"
tools:text="Video Name" />
</LinearLayout>
<LinearLayout
android:id="@+id/content_control"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:orientation="horizontal"
android:visibility="visible"
android:weightSum="3"
tools:visibility="visible">
<ImageButton
android:id="@+id/r10s"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/replay_10s"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:src="@drawable/ic_replay_10"
android:tint="@color/white" />
<ImageButton
android:id="@+id/play_btn"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/play_video"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:src="@drawable/ic_play"
android:tint="@color/white" />
<ImageButton
android:id="@+id/f10s"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/transparent"
android:clickable="true"
android:contentDescription="@string/forward_10s"
android:focusable="true"
android:foreground="@drawable/round_selector"
android:src="@drawable/ic_forward_10"
android:tint="@color/white" />
</LinearLayout>
<SeekBar
android:id="@+id/seekBar"
style="@style/mSeekBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:max="100"
android:progress="0"
android:visibility="visible"
tools:visibility="visible" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Big Player — Fullscreen Approach
Let’s start by initing properties
Code:
class HmsGmsVideoHelper(context: SinglePlayerActivity) : OnPlayWindowListener,
OnWisePlayerListener {
val cntxt = context
val binding: ActivitySinglePlayerBinding
private var isPlaying = false
private val mStopHandler = false
private val player: WisePlayer? by lazy {
showLogInfo(Constants.mHmsGmsVideoHelper, "Player WisePlayerInit")
WisePlayerInit.createPlayer()
}
private val mHandler: Handler by lazy { Handler() }
private val runnable: Runnable by lazy {
Runnable {
configureContentV()
configureControlV()
if (!mStopHandler) {
mHandler.postDelayed(runnable, Constants.DELAY_SECOND)
}
}
}
…
}
init
Code:
init {
binding = ActivitySinglePlayerBinding.inflate(cntxt.layoutInflater)
val view = binding.root
cntxt.setContentView(view)
initUI()
}
initUI()
Code:
private fun initUI() {
showLogInfo(Constants.mHmsGmsVideoHelper, "Player initUI")
binding.layoutVideoIncluder.surfaceView.holder.addCallback(this)
binding.layoutVideoIncluder.surfaceView.holder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS)
cntxt.findViewById<LinearLayout>(R.id.content_social).visibility = View.GONE
player?.setReadyListener(this)
player?.setErrorListener(this)
player?.setEventListener(this)
player?.setResolutionUpdatedListener(this)
player?.setLoadingListener(this)
player?.setPlayEndListener(this)
player?.setSeekEndListener(this)
binding.layoutVideoIncluder.f10s.setOnClickListener {
player?.seek(player?.currentTime!!+10000)
}
binding.layoutVideoIncluder.r10s.setOnClickListener {
player?.seek(player?.currentTime!!-10000)
}
player?.cycleMode = PlayerConstants.CycleMode.MODE_NORMAL
binding.layoutVideoIncluder.extendControl.setOnClickListener {
binding.layoutVideoIncluder.contentInfo.expandView()
binding.layoutVideoIncluder.contentControl.expandView()
binding.layoutVideoIncluder.seekBar.expandView()
if (binding.layoutVideoIncluder.seekBar.visibility == View.GONE)
binding.layoutVideoIncluder.extendControl.setImageResource(R.drawable.ic_arrow_drop_up)
else
binding.layoutVideoIncluder.extendControl.setImageResource(R.drawable.ic_arrow_drop_down)
}
binding.layoutVideoIncluder.playBtn.setOnClickListener {
changePlayState()
}
}
configureContentV()
Code:
private fun configureContentV() {
showLogInfo(
Constants.mHmsGmsVideoHelper,
"Player width: ${player?.videoWidth} - Height: ${player?.videoHeight}"
)
showLogInfo(Constants.mHmsGmsVideoHelper, "Player is playing: ${player?.isPlaying}")
showLogInfo(Constants.mHmsGmsVideoHelper, "Player play mode: ${player?.playMode}")
showLogInfo(
Constants.mHmsGmsVideoHelper, "Player bitrate: ${
player?.currentStreamInfo?.bitrate?.toString()
}"
)
}
configureControlV()
Code:
private fun configureControlV() {
binding.layoutVideoIncluder.seekBar.max = player?.duration!!
binding.layoutVideoIncluder.seekBar.progress = player?.currentTime!!
binding.layoutVideoIncluder.seekBar.secondaryProgress = player?.bufferTime!!
}
changePlayState()
Code:
private fun changePlayState() {
if (isPlaying) {
player?.pause()
isPlaying = false
setPlayView()
} else {
player?.start()
isPlaying = true
setPauseView()
}
}
releasePlayer()
Code:
fun releasePlayer() {
showLogInfo(Constants.mHmsGmsVideoHelper, "Player releasePlayer")
player?.setErrorListener(null)
player?.setEventListener(null)
player?.setResolutionUpdatedListener(null)
player?.setReadyListener(null)
player?.setLoadingListener(null)
player?.setPlayEndListener(null)
player?.setSeekEndListener(null)
player?.release()
}
setPlayView()
Code:
private fun setPlayView() {
binding.layoutVideoIncluder.playBtn.setImageResource(R.drawable.ic_play)
binding.layoutVideoIncluder.lottieView.visibility=View.VISIBLE
}
setPauseView()
Code:
private fun setPauseView() {
binding.layoutVideoIncluder.playBtn.setImageResource(R.drawable.ic_pause)
binding.layoutVideoIncluder.lottieView.visibility=View.GONE
}
readyPlayer(…)
Code:
fun readyPlayer(videoUrl: String, string: String) {
player?.setPlayUrl(videoUrl)
player?.ready()
binding.layoutVideoIncluder.videoName.text = string
}
surfaceCreated(…)
Code:
override fun surfaceCreated(holder: SurfaceHolder?) {
player?.setView(binding.layoutVideoIncluder.surfaceView)
player?.resume(PlayerConstants.ResumeType.KEEP)
}
surfaceChanged(…)
Code:
override fun surfaceChanged(holder: SurfaceHolder?, format: Int, width: Int, height: Int) {
player?.setSurfaceChange()
}
surfaceDestroyed(…)
Code:
override fun surfaceDestroyed(holder: SurfaceHolder?) {
player?.suspend()
}
onSurfaceTextureAvailable(…)
Code:
override fun onSurfaceTextureAvailable(surface: SurfaceTexture?, width: Int, height: Int) {
player?.resume(PlayerConstants.ResumeType.KEEP)
}
onReady(…)
Code:
override fun onReady(p0: WisePlayer?) {
showLogInfo(Constants.mHmsGmsVideoHelper, "On Ready")
player?.start()
isPlaying = true
cntxt.runOnUiThread {
configureControlV()
mHandler.postDelayed(runnable, Constants.DELAY_SECOND)
}
}
onPlayEnd(…)
Code:
override fun onPlayEnd(p0: WisePlayer?) {
showLogInfo(Constants.mHmsGmsVideoHelper, "onPlayEnd")
isPlaying = false
setPlayView()
}
onStartTrackingTouch(…)
Code:
override fun onStartTrackingTouch(seekBar: SeekBar?) {
seekBar?.progress?.let { player?.seek(it) }
}
onStartTrackingTouch(…)
Code:
override fun onStopTrackingTouch(seekBar: SeekBar?) {
seekBar?.progress?.let { player?.seek(it) }
}
Comparing Exo Player with HMS Video Kit
ExoPlayer
Easy to use lightweight, easy to implement
For audio-only playback on some devices consumes more battery than standard MediaPlayer.
Support for DASH and SmoothStreaming, HLS
Uses Widevine common encryption.
API level of at least 23 required to operate
Video stutters when it fills the screen.
Initial video loads almost instantly but the speed of loading degrades over the length of the video.
Supports playing “m3u8” live stream formats.
Reference for Exo Player
HMS Video Kit
Initial implementations were sometimes complicate things.
For audio-only playback on some devices consumes more battery than standard MediaPlayer.
Supports 3GP, MP4, or TS format and compliant with HTTP/HTTPS, HLS, or DASH. Local videos are not supported.
Uses WisePlay DRM
HMS Core (APK) 4.0.1.300 or later and above is required to operate
Video operates normally on a filled screen.
Initial video load takes time but video almost never falls back down to loading state over the length of the video.
Reference for HMS Video Kit
References
Firebase Realtime Database
Firebase Cloud Storage
Picasso
Okhttp
Okio
Lottie
AndroidSlidingUpPanel
Slidr
CircleImageView
BottomBar
AnimatedBottomBar
Dagger
CameraX
Simple Video View
Youtube Extractor
Mockito
HMS
Video Kit
GMS
Exo Player 2.0
------------------------------
END OF PART - VIII
nice
can i change the streamed music equaliser?

Expert: Courier App MVVM Jetpack (HMS Location and Map Kit) in Android using Kotlin- Part-5

Overview
In this article, I will create a Courier android application using Kotlin in which I will integrate HMS Core kits such as HMS Account, Push, CloudDB, AuthService, Push Kit Uplink Message, Location and Map Kit.
We have integrated HMS Account and AuthService Kit in part-, Push Notification Using HMS Push Kit in Part-2, Cloud DB Kit in Part-3 and Huawei Client Push integration in Part4 of this series. Kindly go through the link below-
Part-1 https://forums.developer.huawei.com/forumPortal/en/topic/0202841957497640128
Part-2 https://forums.developer.huawei.com/forumPortal/en/topic/0201847982965230092
part-3 https://forums.developer.huawei.com/forumPortal/en/topic/0201854022878900124?fid=0101187876626530001
App will make use of android MVVM clean architecture using Jetpack components such as DataBinding, AndroidViewModel, Observer, LiveData and much more.
In this article, we are going to implement DataBinding using Observable pattern.
Map Kit Introduction
Map Kit covers map data of more than 200 countries and regions, and supports over 70 languages. User can easily integrate map-based functions into your apps using SDK. It optimizes and enriches the map detail display capability. Map Kit supports gestures including zoom, rotation, moving and tilt gestures to ensure smooth interaction experience.
Location Kit introduction
Location Kit combines the GPS, Wi-Fi and base station location functionalities in your app to build up global positioning capabilities, allows to provide flexible location-based services targeted at users around globally. Currently, it provides three main capabilities: fused location, activity identification and geo-fence. You can call one or more of these capabilities as required.
Prerequisite
Huawei Phone EMUI 3.0 or later.
Non-Huawei phones Android 4.4 or later (API level 19 or higher).
HMS Core APK 4.0.0.300 or later
Android Studio
AppGallery Account
App Gallery Integration process
Sign In and Create or Choose a project on AppGallery Connect portal.
{
"lightbox_close": "Close",
"lightbox_next": "Next",
"lightbox_previous": "Previous",
"lightbox_error": "The requested content cannot be loaded. Please try again later.",
"lightbox_start_slideshow": "Start slideshow",
"lightbox_stop_slideshow": "Stop slideshow",
"lightbox_full_screen": "Full screen",
"lightbox_thumbnails": "Thumbnails",
"lightbox_download": "Download",
"lightbox_share": "Share",
"lightbox_zoom": "Zoom",
"lightbox_new_window": "New window",
"lightbox_toggle_sidebar": "Toggle sidebar"
}
Navigate to Project settings and download the configuration file.
Navigate to General Information, and then provide Data Storage location.
App Development
Add Required Dependencies:
Launch Android studio and create a new project. Once the project is ready.
Navigate to the Gradle scripts folder and open build.gradle (project: app).
Code:
ext.kotlin_version = "1.4.21"
repositories {
google()
jcenter()
maven {url 'https://developer.huawei.com/repo/'}
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.1"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.huawei.agconnect:agcp:1.4.2.300'
Add following dependency for HMS Loaction and Map Kits
// Huawei Map
implementation 'com.huawei.hms:maps:6.2.0.301'
// Huawei Location Kit
implementation 'com.huawei.hms:location:6.2.0.300'
Navigate to the Gradle scripts folder and open build.gradle (module: app).
Code:
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply plugin: 'com.huawei.agconnect'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 31
buildToolsVersion "29.0.3" buildFeatures {
dataBinding = true
} compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
} defaultConfig {
applicationId "com.hms.corrierapp"
minSdkVersion 27
targetSdkVersion 31
versionCode 1
versionName "1.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
} buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.4.1'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
implementation "android.arch.lifecycle:extensions:1.1.1" implementation 'com.google.android.material:material:1.2.0' implementation 'com.squareup.retrofit2:retrofit:2.6.2'
implementation 'com.squareup.retrofit2:converter-gson:2.5.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.5.0'
implementation 'com.squareup.okhttp3:logging-interceptor:4.2.2'
implementation(
[group: 'com.fasterxml.jackson.core', name: 'jackson-core', version: '2.4.1'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-annotations', version: '2.4.1'],
[group: 'com.fasterxml.jackson.core', name: 'jackson-databind', version: '2.4.1'],
) //HMS Kits
implementation 'com.huawei.agconnect:agconnect-core:1.5.0.300'
implementation 'com.huawei.hms:hwid:5.3.0.302' implementation 'com.huawei.hms:push:4.0.3.301' implementation 'com.huawei.agconnect:agconnect-cloud-database:1.5.0.300'
implementation "com.huawei.agconnect:agconnect-auth-huawei:1.6.0.300"
implementation 'com.huawei.agconnect:agconnect-auth:1.5.0.300' // Huawei Map
implementation 'com.huawei.hms:maps:6.2.0.301'
// Huawei Location Kit
implementation 'com.huawei.hms:location:6.2.0.300'}
Code Implementation
Created following package model, push, viewmodel.
Model: In your primary folder, create a new package and name it model.
MapActivity.kt:
Code:
package com.hms.corrierapp.map
import android.Manifest
import android.content.IntentSender
import android.content.pm.PackageManager
import android.location.Location
import android.os.*
import android.util.Log
import android.widget.Chronometer
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import com.hms.corrierapp.R
import com.huawei.hmf.tasks.Task
import com.huawei.hms.common.ApiException
import com.huawei.hms.common.ResolvableApiException
import com.huawei.hms.location.*
import com.huawei.hms.maps.*
import com.huawei.hms.maps.model.*
import java.io.File
import java.text.DecimalFormat
class MapActivity : AppCompatActivity(), OnMapReadyCallback {
private lateinit var mMapView: MapView
private var mHwMap: HuaweiMap? = null
private var mPolylineOptions: PolylineOptions? = null
private var mListener: LocationSource.OnLocationChangedListener? = null
private var mMarkerStart: Marker? = null
private var mMarkerEnd: Marker? = null
private var mLocationRequest: LocationRequest? = null
private var mTvStart: TextView? = null
private var mTvDistance: TextView? = null
private var mTime: Chronometer? = null
private var fusedLocationProviderClient: FusedLocationProviderClient? = null
private val mPath: PathBean = PathBean()
private var mSeconds: Long = 0
private val mHandler = Handler(Looper.getMainLooper())
private val mDecimalFormat = DecimalFormat("0.00")
private var mIsRunning = false
private val mTimeRunnable: Runnable = object : Runnable {
override fun run() {
mTime!!.text = formatSeconds()
mHandler.postDelayed(this, 1000)
}
}
private var mLocationCallback: LocationCallback? = null
// GPS Data
private var mGpsDataThread: HandlerThread? = null
private var mGpsDataHandler: Handler? = null
private var mGpsDataFile: File? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_map)
checkPermission()
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(this)
// check location settings
checkLocationSettings()
// init MapView
mMapView = findViewById(R.id.hw_mapview)
var mapViewBundle: Bundle? = null
if (savedInstanceState != null) {
mapViewBundle = savedInstanceState.getBundle(MAPVIEW_BUNDLE_KEY)
}
mMapView.onCreate(mapViewBundle)
mMapView.getMapAsync(this)
mTvDistance = findViewById(R.id.tv_distance)
mTime = findViewById(R.id.cm_time)
mTvStart = findViewById(R.id.tv_start)
mTvStart!!.setOnClickListener({ processStartClick() })
// Initializing Map Kit Polyline
mPolylineOptions = PolylineOptions()
mPolylineOptions!!.color(resources.getColor(R.color.colorAccent))
mPolylineOptions!!.width(5f)
// Recording GPS Data
mGpsDataFile = File(getExternalFilesDir(null), "GpsData.txt")
}
private fun checkPermission() {
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.P) {
Log.i(TAG, "sdk <= 28 Q")
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED
&& ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
val strings = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION
)
ActivityCompat.requestPermissions(this, strings, 1)
}
} else {
// Dynamically apply for permissions required for SDK > 28. Add the android.permission.ACCESS_BACKGROUND_LOCATION permission.
if (ActivityCompat.checkSelfPermission(
this,
Manifest.permission.ACCESS_FINE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission
(
this,
Manifest.permission.ACCESS_COARSE_LOCATION
) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission
(
this,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
) != PackageManager.PERMISSION_GRANTED
) {
val strings = arrayOf(
Manifest.permission.ACCESS_FINE_LOCATION,
Manifest.permission.ACCESS_COARSE_LOCATION,
Manifest.permission.ACCESS_BACKGROUND_LOCATION
)
ActivityCompat.requestPermissions(this, strings, 2)
}
}
}
override fun onMapReady(huaweiMap: HuaweiMap?) {
Log.d(TAG, "onMapReady: ")
mHwMap = huaweiMap
mHwMap?.isMyLocationEnabled = true
mHwMap?.uiSettings?.isZoomControlsEnabled = false
// Add Location Source
mHwMap?.setLocationSource(object : LocationSource {
override fun activate(onLocationChangedListener: LocationSource.OnLocationChangedListener?) {
mListener = onLocationChangedListener
}
override fun deactivate() {}
})
// Obtains the current position and updates the map camera.
try {
val lastLocation: Task<Location> = fusedLocationProviderClient!!.lastLocation
lastLocation.addOnSuccessListener { location ->
if (location ==null) [email protected]
mListener!!.onLocationChanged(location)
if (mListener != null) {
mListener!!.onLocationChanged(location)
val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom(
LatLng(location.latitude, location.longitude), 15f
)
mHwMap!!.animateCamera(cameraUpdate)
}
}.addOnFailureListener {
Log.d(TAG, "onMapReady: Obtains the current position failure")
}
} catch (e: Exception) {
}
}
companion object {
private const val MAPVIEW_BUNDLE_KEY = "MapViewBundleKey"
private const val TAG = "MapActivity"
}
override fun onStart() {
super.onStart()
mMapView.onStart()
}
override fun onStop() {
super.onStop()
mMapView.onStop()
}
override fun onDestroy() {
removeLocationUpdatesWithCallback()
super.onDestroy()
mHandler.removeCallbacksAndMessages(null)
mMapView.onDestroy()
}
override fun onPause() {
mMapView.onPause()
super.onPause()
mGpsDataThread!!.quitSafely()
try {
mGpsDataThread!!.join()
mGpsDataThread = null
mGpsDataHandler = null
} catch (e: InterruptedException) {
e.printStackTrace()
}
}
override fun onResume() {
super.onResume()
mMapView.onResume()
mGpsDataThread = HandlerThread("DotThread")
mGpsDataThread!!.start()
mGpsDataHandler = Handler(mGpsDataThread!!.looper)
}
override fun onLowMemory() {
super.onLowMemory()
mMapView.onLowMemory()
}
private fun checkLocationSettings() {
val builder: LocationSettingsRequest.Builder = LocationSettingsRequest.Builder()
val locationSettingsRequest: LocationSettingsRequest = builder.build()
val settingsClient: SettingsClient = LocationServices.getSettingsClient(this)
settingsClient.checkLocationSettings(locationSettingsRequest)
.addOnSuccessListener { requestLocationUpdate() }.addOnFailureListener { e ->
when ((e as ApiException).statusCode) {
LocationSettingsStatusCodes.RESOLUTION_REQUIRED -> try {
val rae: ResolvableApiException = e as ResolvableApiException
rae.startResolutionForResult([email protected], 0)
} catch (sie: IntentSender.SendIntentException) {
}
}
}
}
private fun requestLocationUpdate() {
mLocationRequest = LocationRequest()
mLocationRequest!!.interval = 5000
mLocationRequest!!.priority = LocationRequest.PRIORITY_HIGH_ACCURACY
mLocationCallback = object : LocationCallback() {
override fun onLocationResult(locationResult: LocationResult) {
super.onLocationResult(locationResult)
writeGpsData2Sdcard(locationResult.lastLocation)
if (mIsRunning) {
processLocationChange(locationResult.lastLocation)
}
}
override fun onLocationAvailability(locationAvailability: LocationAvailability?) {
super.onLocationAvailability(locationAvailability)
}
}
fusedLocationProviderClient
?.requestLocationUpdates(mLocationRequest, mLocationCallback, Looper.getMainLooper())
?.addOnSuccessListener { Log.i(TAG, "request location updates success") }
?.addOnFailureListener { e ->
Log.e(TAG, "request location updates failed, error: " + e.message)
}
}
// Removed when the location update is no longer required.
private fun removeLocationUpdatesWithCallback() {
try {
val voidTask: Task<Void> =
fusedLocationProviderClient!!.removeLocationUpdates(mLocationCallback)
voidTask.addOnSuccessListener { }.addOnFailureListener { }
} catch (e: Exception) {
Log.e(TAG, "removeLocationUpdatesWithCallback Exception : $e")
}
}
private fun processStartClick() {
if (mIsRunning) {
mIsRunning = false
mPath.endTime = (System.currentTimeMillis())
mTvStart!!.text = "Start"
mHandler.removeCallbacks(mTimeRunnable)
if (mPath.mPathLinePoints!!.size > 0) {
mPath.endPoint = (mPath.mPathLinePoints!!.get(mPath.mPathLinePoints!!.size - 1))
if (null != mMarkerStart && null != mMarkerEnd) {
mMarkerStart!!.remove()
mMarkerEnd!!.remove()
}
val startPointOptions: MarkerOptions = MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_location_marker))
.position(mPath.mStartPoint)
startPointOptions.title("Start Point")
startPointOptions.snippet("Start Point")
mMarkerStart = mHwMap!!.addMarker(startPointOptions)
val endPointOptions: MarkerOptions = MarkerOptions()
.icon(BitmapDescriptorFactory.fromResource(R.drawable.ic_location_marker))
.position(mPath.mEndPoint)
endPointOptions.title("End Point")
endPointOptions.snippet("End Point")
mMarkerEnd = mHwMap!!.addMarker(endPointOptions)
}
} else {
mIsRunning = true
mPath.reset()
mPath.startTime = System.currentTimeMillis()
mHandler.post(mTimeRunnable)
mTvStart!!.text = "Stop"
}
}
private fun processLocationChange(location: android.location.Location) {
val latLng = LatLng(location.latitude, location.longitude)
if (mPath.mStartPoint == null) {
mPath.mStartPoint = latLng
}
mPath.addPoint(latLng)
val distance: Float = mPath.updateDistance()
val sportMile = distance / 1000.0
if (mSeconds > 0) {
val distribution = mSeconds.toDouble() / 60.0 / sportMile
mPath.setDistribution(distribution)
mTvDistance!!.text = mDecimalFormat.format(sportMile)
} else {
mPath.setDistribution(0.0)
mTvDistance!!.text = "0.00"
}
mPolylineOptions!!.add(latLng)
mHwMap!!.addPolyline(mPolylineOptions)
if (mListener != null) {
mListener!!.onLocationChanged(location)
val cameraUpdate: CameraUpdate = CameraUpdateFactory.newLatLngZoom(
LatLng(location.latitude, location.longitude), 15f
)
mHwMap!!.animateCamera(cameraUpdate)
}
}
fun formatSeconds(): String {
val hh = if (mSeconds / 3600 > 9) mSeconds / 3600 else mSeconds / 3600
val mm =
if (mSeconds % 3600 / 60 > 9) mSeconds % 3600 / 60 else mSeconds % 3600 / 60
mSeconds++
return "$hh:$mm"
}
private fun writeGpsData2Sdcard(location: Location) {
Log.d(
TAG,
"write latitude and longitude, latitude: " + location.latitude + ", longitude: " + location.longitude
)
mGpsDataHandler!!.post(
GpsDataSaver(
mGpsDataFile, """
${location.latitude}, ${location.longitude}
""".trimIndent()
)
)
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
}
}
}
activity_map.xml:
Code:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:map="http://schemas.android.com/apk/res-auto"
android:id="@+id/sport_content"
android:layout_width="match_parent"
android:layout_height="match_parent"> <com.huawei.hms.maps.MapView
android:id="@+id/hw_mapview"
android:layout_width="match_parent"
android:layout_height="match_parent"
map:mapType="normal"
map:uiCompass="true"
map:uiZoomControls="true" /> <LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:layout_marginStart="15dp"
android:layout_marginTop="60dp"
android:layout_marginEnd="15dp"
android:background="@color/white"
android:orientation="horizontal"> <LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_weight="1"
android:orientation="vertical"> <TextView
android:id="@+id/tv_distance"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ellipsize="end"
android:gravity="center_horizontal"
android:maxLength="8"
android:text="0.00"
android:textColor="#000000"
android:textSize="25sp"
android:textStyle="bold" /> <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginBottom="2.5dp"
android:gravity="center_horizontal"
android:text="km"
android:textColor="#88000000"
android:textSize="18sp" />
</LinearLayout> <LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:layout_marginTop="20dp"
android:layout_weight="2"
android:orientation="vertical"> <Chronometer
android:id="@+id/cm_time"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="20dp"
android:ellipsize="end"
android:format="00:00"
android:gravity="center"
android:textColor="#000000"
android:textSize="22sp"
android:textStyle="bold" /> <TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginBottom="2.5dp"
android:gravity="center_horizontal"
android:text="Total time"
android:textColor="#88000000"
android:textSize="18sp" />
</LinearLayout> </LinearLayout> <TextView
android:id="@+id/tv_start"
android:layout_width="100dp"
android:layout_height="100dp"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:layout_marginBottom="30dp"
android:gravity="center"
android:text="Track Courier Location"
android:textColor="@color/colorPrimary"
android:textSize="21sp"
android:textStyle="bold" /></RelativeLayout>
activity_delivery_status.xml:
Code:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"> <RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/white"> <ScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_centerVertical="true"> <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:padding="5dp"
android:text="@string/delivery"
android:textAlignment="center"
android:textColor="@color/colorPrimaryDark"
android:textSize="34sp"
android:textStyle="bold" /> <LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"> <LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.1"
android:gravity="center"
android:orientation="vertical"> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_location_marker" /> <View
android:layout_width="5dp"
android:layout_height="match_parent"
android:background="@android:color/black" /> </LinearLayout> <LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:orientation="vertical"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Booking"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="20sp"
android:textStyle="bold" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Done"
android:textAlignment="center"
android:textColor="@color/green"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Payment Pending"
android:textAlignment="center"
android:textColor="@color/red"
android:textSize="18sp"
android:textStyle="bold" /> </LinearLayout> </LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"> <LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.1"
android:gravity="center"
android:orientation="vertical"> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_location_marker" /> <View
android:layout_width="5dp"
android:layout_height="match_parent"
android:background="@android:color/black" /> </LinearLayout> <LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:orientation="vertical"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="PickUp"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="20sp"
android:textStyle="bold" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Done"
android:textAlignment="center"
android:textColor="@color/green"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Shipping"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="18sp"
android:textStyle="bold" /> </LinearLayout> </LinearLayout> <LinearLayout
android:layout_width="match_parent"
android:layout_height="100dp"
android:orientation="horizontal"> <LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.1"
android:gravity="center"
android:orientation="vertical"> <ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_location_marker" /> <View
android:layout_width="5dp"
android:layout_height="match_parent"
android:background="@android:color/black" /> </LinearLayout> <LinearLayout
android:layout_width="0dp"
android:layout_height="100dp"
android:layout_weight="0.9"
android:gravity="center_vertical"
android:orientation="vertical"> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Delivery"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="20sp"
android:textStyle="bold" /> <TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="On It's Way"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="18sp"
android:textStyle="bold" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="5dp"
android:text="Tracking ID: 12231223"
android:textAlignment="center"
android:textColor="@color/gray"
android:textSize="18sp"
android:textStyle="bold" /> </LinearLayout> </LinearLayout>
<Button
android:id="@+id/btn_done"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:layout_marginBottom="5dp"
android:background="@color/colorPrimaryDark"
android:text="See On Map"
android:textColor="@color/white"
android:textStyle="bold" />
</LinearLayout> </ScrollView> </RelativeLayout>
</layout>
App Build Result
Tips and Tricks
Set minSDK version to 24 or later, otherwise you will get AndriodManifest merge issue.
Make sure you have added the agconnect-services.json file to app folder.
Make sure you have added SHA-256 fingerprint without fail.
Make sure all the dependencies are added properly.
Conclusion
In this article, we have learned how to integrate HMS Account, Push, CloudDB, AuthService, Push Kit Uplink Message, Location and Map Kit in Android application. After completely read this article user can easily implement Location and Map Kit in the Courier android application using Kotlin.
Thanks for reading this article. Be sure to like and comment to this article, if you found it helpful. It means a lot to me.
References
HMS Docs:
https://developer.huawei.com/consum.../HMSCore-Guides/introduction-0000001050048870
https://developer.huawei.com/consum...droid-sdk-brief-introduction-0000001061991343
https://developer.huawei.com/consum...-Guides/android-introduction-0000001121930588
Location and Map Kit Training Video:
https://developer.huawei.com/consumer/en/training/course/video/201575277450653242

Categories

Resources