Related
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 ?
{
"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
{
"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
{
"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
{
"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"
}
Introduction
In this article, we can learn about the basic questions on Android and HMS of Quiz application. The purpose of conducting quizzes within the business is more fun and educative to understand how the business is running straight away, test the knowledge of the user and helps to form a businessman or woman a far better person in terms of the business process. So, I will provide the series of articles on this Quiz App, in upcoming articles.
If you are new to this application, follow my previous article.
https://forums.developer.huawei.com/forumPortal/en/topic/0202877278014350004
Requirements
1. Any operating system (MacOS, Linux and Windows).
2. Must have a Huawei phone with HMS 4.0.0.300 or later.
3. Must have a laptop or desktop with Android Studio, Jdk 1.8, SDK platform 26 and Gradle 4.6 and above installed.
4. Minimum API Level 24 is required.
5. Required EMUI 9.0.0 and later version devices.
How to integrate HMS Dependencies
1. First register as Huawei developer and complete identity verification in Huawei developers website, refer to register a Huawei ID.
2. Create a project in android studio, refer Creating an Android Studio Project.
3. Generate a SHA-256 certificate fingerprint.
4. To generate SHA-256 certificate fingerprint. On right-upper corner of android project click Gradle, choose Project Name > Tasks > android, and then click signingReport, as follows.
Note: Project Name depends on the user created name.
5. Create an App in AppGallery Connect.
6. Download the agconnect-services.json file from App information, copy and paste in android Project under app directory, as follows.
7. Enter SHA-256 certificate fingerprint and click Save button, as follows.
Note: Above steps from Step 1 to 7 is common for all Huawei Kits.
8. Add the below maven URL in build.gradle(Project) file under the repositories of buildscript, dependencies and allprojects, refer Add Configuration.
Java:
maven { url 'http://developer.huawei.com/repo/' }
classpath 'com.huawei.agconnect:agcp:1.6.0.300'
9. Add the below plugin and dependencies in build.gradle(Module) file.
Java:
apply plugin: id 'com.huawei.agconnect'
// Huawei AGC
implementation 'com.huawei.agconnect:agconnect-core:1.6.0.300'
10. Now Sync the gradle.
Let us move to development
I have created a project on Android studio with empty activity let us start coding.
In the AndroidActivity.kt we can find the business logic for android questions.
Java:
class AndroidActivity : AppCompatActivity(), View.OnClickListener {
private var mCurrentPosition: Int = 1
private var mQuestionsList: ArrayList<AndroidQuestions>? = null
private var mSelectedOptionPosition: Int = 0
private var mCorrectAnswers: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_android)
mQuestionsList = AndroidConstants.getQuestions()
setQuestion()
txt_option1.setOnClickListener(this)
txt_option2.setOnClickListener(this)
txt_option3.setOnClickListener(this)
txt_option4.setOnClickListener(this)
btn_ardsubmit.setOnClickListener(this)
}
private fun setQuestion() {
val question = mQuestionsList!![mCurrentPosition -1]
defaultOptionsView()
if(mCurrentPosition == mQuestionsList!!.size){
btn_ardsubmit.text = "FINISH"
} else {
btn_ardsubmit.text = "SUBMIT"
}
progressBar_ard.progress = mCurrentPosition
txt_progress_ard.text = "$mCurrentPosition" + "/" + progressBar_ard.max
txt_ardquestion.text = question!!.questions
// iv_image.setImageResource(question.image)
txt_option1.text = question.option1
txt_option2.text = question.option2
txt_option3.text = question.option3
txt_option4.text = question.option4
}
private fun defaultOptionsView() {
val options = ArrayList<TextView>()
options.add(0, txt_option1)
options.add(1, txt_option2)
options.add(2, txt_option3)
options.add(3, txt_option4)
for(option in options){
option.setTextColor(Color.parseColor("#7A8089"))
option.typeface = Typeface.DEFAULT
option.background = ContextCompat.getDrawable(this, R.drawable.border_bg)
}
}
override fun onClick(v: View?) {
when(v?.id){
R.id.txt_option1 -> {
selectedOptionView(txt_option1, 1)
}
R.id.txt_option2 -> {
selectedOptionView(txt_option2, 2)
}
R.id.txt_option3 -> {
selectedOptionView(txt_option3, 3)
}
R.id.txt_option4 -> {
selectedOptionView(txt_option4, 4)
}
R.id.btn_ardsubmit -> {
if(mSelectedOptionPosition == 0){
mCurrentPosition++
when{
mCurrentPosition <= mQuestionsList!!.size -> {
setQuestion()
} else -> {
val intent = Intent(this, AndroidResultActivity::class.java)
intent.putExtra(AndroidConstants.TOTAL_ARD_QUESTIONS, mCorrectAnswers)
intent.putExtra(AndroidConstants.CORRECT_ARD_ANSWERS, mQuestionsList!!.size)
startActivity(intent)
}
}
} else {
val question = mQuestionsList?.get(mCurrentPosition -1)
if(question!!.correctAnswer != mSelectedOptionPosition){
answerView(mSelectedOptionPosition, R.drawable.wrong_bg)
} else {
mCorrectAnswers ++
}
answerView(question!!.correctAnswer, R.drawable.correct_bg)
if(mCurrentPosition == mQuestionsList!!.size){
btn_ardsubmit.text = "FINISH"
} else {
btn_ardsubmit.text = "Next Question"
}
mSelectedOptionPosition = 0
}
}
}
}
private fun answerView(answer:Int, drawableView:Int){
when(answer) {
1 -> {
txt_option1.background = ContextCompat.getDrawable(this, drawableView)
}
2 -> {
txt_option2.background = ContextCompat.getDrawable(this, drawableView)
}
3 -> {
txt_option3.background = ContextCompat.getDrawable(this, drawableView)
}
4 -> {
txt_option4.background = ContextCompat.getDrawable(this, drawableView)
}
}
}
private fun selectedOptionView(tv: TextView, selectedOptionNum: Int){
defaultOptionsView()
mSelectedOptionPosition = selectedOptionNum
tv.setTextColor(Color.parseColor("#363A43"))
tv.setTypeface(tv.typeface, Typeface.BOLD)
tv.background = ContextCompat.getDrawable(this, R.drawable.select_bg)
}
}
In the AndroidResultActivity.kt we can find the business logic for android result view.
Java:
class AndroidResultActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_android_result)
val totalQuestions = intent.getIntExtra(AndroidConstants.TOTAL_ARD_QUESTIONS, 0)
val correctAnswers = intent.getIntExtra(AndroidConstants.CORRECT_ARD_ANSWERS, 0)
// txt_ardscore.text = "Your Score is $correctAnswers out of $totalQuestions"
txt_ardscore.text = "Your Score is $totalQuestions out of $correctAnswers"
btn_ardfinish.setOnClickListener {
startActivity(Intent(this, Home::class.java))
}
}
}
Create a data class AndroidQuestions.kt to access variables.
Code:
data class AndroidQuestions(
val id:Int,
val questions:String,
val option1:String,
val option2:String,
val option3:String,
val option4:String,
val correctAnswer:Int
)
Create object class AndroidConstants.kt to show questions.
Code:
object AndroidConstants {
const val TOTAL_ARD_QUESTIONS: String = "total_questions"
const val CORRECT_ARD_ANSWERS: String = "correct_answers"
fun getQuestions():ArrayList<AndroidQuestions>{
val questionsList = ArrayList<AndroidQuestions>()
// 1
val ques1 = AndroidQuestions(1, "On which thread broadcast receivers will work in android?",
"Worker Thread","Main Thread", "Activity Thread", "None of the above", 2)
questionsList.add(ques1)
// 2
val ques2 = AndroidQuestions(2, "What was the first phone released that ran the Android OS?",
"Google gPhone","HTC Hero", "Motorola Droid", "T-Mobile G1", 4)
questionsList.add(ques2)
// 3
val ques3 = AndroidQuestions(3, "During an Activity life-cycle, what is the first callback method invoked by the system?",
"onCreate()", "onStart()", "onStop()", "onDestroy()", 1)
questionsList.add(ques3)
// 4
val ques4 = AndroidQuestions(4, "What is mean by ADB?",
"Application Debug Bridge", "Android Debug Bridge", "Application data bridge", "Android data bridge", 2)
questionsList.add(ques4)
// 5
val ques5 = AndroidQuestions(5, "If you want to increase the whitespace between widgets, you will need to use the ___ property",
"Android:digits", "Android:capitalize", "Android:padding", "Android:autoText", 3)
questionsList.add(ques5)
// 6
val ques6 = AndroidQuestions(6, "What is ANR responding time in android?",
"10 seconds", "5 seconds", "1 minute", "45 seconds", 2)
questionsList.add(ques6)
// 7
val ques7 = AndroidQuestions(7, "Choose the layout, that is deprecated",
"Absolute Layout", "Frame Layout", "Relative Layout", "Linear Layout", 2)
questionsList.add(ques7)
// 8
val ques8 = AndroidQuestions(8, "Which method can access a view element of a layout resource in activity",
"onCreate()", "findViewById()", "setContentView()", "None", 2)
questionsList.add(ques8)
// 9
val ques9 = AndroidQuestions(9, "In Which Directory XML Layout files are stored",
"/assets", "/src", "/res/values", "/res/layout", 4)
questionsList.add(ques9)
// 10
val ques10 = AndroidQuestions(10, "What are the functionalities in AsyncTask in Android?",
"onPreExecution()", "onPostExecution()", "DoInBackground()", "onProgressUpdate()", 2)
questionsList.add(ques10)
return questionsList
}
}
In the HMSActivity.kt we can find the business logic for android questions.
Java:
class HMSActivity : AppCompatActivity(),View.OnClickListener {
private var kCurrentPosition: Int = 1
private var kQuestionsList: ArrayList<HMSQuestions>? = null
private var kSelectedOptionPosition: Int = 0
private var kCorrectAnswers: Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hmsactivity)
kQuestionsList = HMSConstants.getQuestions()
setQuestion()
txt_option1_hms.setOnClickListener(this)
txt_option2_hms.setOnClickListener(this)
txt_option3_hms.setOnClickListener(this)
txt_option4_hms.setOnClickListener(this)
btn_hms_submit.setOnClickListener(this)
}
private fun setQuestion() {
val question = kQuestionsList!![kCurrentPosition -1]
defaultOptionsView()
if(kCurrentPosition == kQuestionsList!!.size){
btn_hms_submit.text = "FINISH"
} else {
btn_hms_submit.text = "SUBMIT"
}
progressBar_hms.progress = kCurrentPosition
txt_progress_hms.text = "$kCurrentPosition" + "/" + progressBar_hms.max
txt_hmsquestion.text = question!!.hms_questions
// iv_image.setImageResource(question.image)
txt_option1_hms.text = question.hms_option1
txt_option2_hms.text = question.hms_option2
txt_option3_hms.text = question.hms_option3
txt_option4_hms.text = question.hms_option4
}
private fun defaultOptionsView() {
val options = ArrayList<TextView>()
options.add(0, txt_option1_hms)
options.add(1, txt_option2_hms)
options.add(2, txt_option3_hms)
options.add(3, txt_option4_hms)
for(option in options){
option.setTextColor(Color.parseColor("#7A8089"))
option.typeface = Typeface.DEFAULT
option.background = ContextCompat.getDrawable(this, R.drawable.border_bg)
}
}
override fun onClick(v: View?) {
when(v?.id){
R.id.txt_option1_hms -> {
selectedOptionView(txt_option1_hms, 1)
}
R.id.txt_option2_hms -> {
selectedOptionView(txt_option2_hms, 2)
}
R.id.txt_option3_hms -> {
selectedOptionView(txt_option3_hms, 3)
}
R.id.txt_option4_hms -> {
selectedOptionView(txt_option4_hms, 4)
}
R.id.btn_hms_submit -> {
if(kSelectedOptionPosition == 0){
kCurrentPosition++
when{
kCurrentPosition <= kQuestionsList!!.size -> {
setQuestion()
} else -> {
val intent = Intent(this, HMSResultActivity::class.java)
intent.putExtra(HMSConstants.TOTAL_HMS_QUESTIONS, kCorrectAnswers)
intent.putExtra(HMSConstants.CORRECT_HMS_ANSWERS, kQuestionsList!!.size)
startActivity(intent)
}
}
} else {
val question = kQuestionsList?.get(kCurrentPosition -1)
if(question!!.hms_correctAnswer != kSelectedOptionPosition){
answerView(kSelectedOptionPosition, R.drawable.wrong_bg)
} else {
kCorrectAnswers ++
}
answerView(question!!.hms_correctAnswer, R.drawable.correct_bg)
if(kCurrentPosition == kQuestionsList!!.size){
btn_hms_submit.text = "FINISH"
} else {
btn_hms_submit.text = "Next Question"
}
kSelectedOptionPosition = 0
}
}
}
}
private fun answerView(answer:Int, drawableView:Int){
when(answer) {
1 -> {
txt_option1_hms.background = ContextCompat.getDrawable(this, drawableView)
}
2 -> {
txt_option2_hms.background = ContextCompat.getDrawable(this, drawableView)
}
3 -> {
txt_option3_hms.background = ContextCompat.getDrawable(this, drawableView)
}
4 -> {
txt_option4_hms.background = ContextCompat.getDrawable(this, drawableView)
}
}
}
private fun selectedOptionView(tv: TextView, selectedOptionNum: Int){
defaultOptionsView()
kSelectedOptionPosition = selectedOptionNum
tv.setTextColor(Color.parseColor("#363A43"))
tv.setTypeface(tv.typeface, Typeface.BOLD)
tv.background = ContextCompat.getDrawable(this, R.drawable.select_bg)
}
}
In the HMSResultActivity.kt we can find the business logic for android result view.
Code:
class HMSResultActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_hmsresult)
val totalQuestions = intent.getIntExtra(HMSConstants.TOTAL_HMS_QUESTIONS, 0)
val correctAnswers = intent.getIntExtra(HMSConstants.CORRECT_HMS_ANSWERS, 0)
txt_hmsscore.text = "Your Score is $totalQuestions out of $correctAnswers"
btn_hmsfinish.setOnClickListener {
startActivity(Intent(this, Home::class.java))
}
}
}
Create a data class HMSQuestions.kt to access variables.
Code:
data class HMSQuestions(
val id:Int,
val hms_questions:String,
val hms_option1:String,
val hms_option2:String,
val hms_option3:String,
val hms_option4:String,
val hms_correctAnswer:Int
)
Create object class HMSConstants.kt to show questions.
Code:
object HMSConstants {
const val TOTAL_HMS_QUESTIONS: String = "total_questions"
const val CORRECT_HMS_ANSWERS: String = "correct_answers"
fun getQuestions():ArrayList<HMSQuestions>{
val hmsquestionsList = ArrayList<HMSQuestions>()
// 1
val hques1 = HMSQuestions(1, "What is HMS?",
"Huawei Medical Services","Huawei Mobile Services", "Huawei Mission Services", "Huawei Military Services", 2)
hmsquestionsList.add(hques1)
// 2
val hques2 = HMSQuestions(2, "Which Kit provides a secure login authorization function of users?",
"Map Kit","Account Kit", "Location Kit", "Scan Kit", 2)
hmsquestionsList.add(hques2)
// 3
val hques3 = HMSQuestions(3, "Scan Kit supports -- formats for most business scenarios.",
"12", "14", "15", "13", 4)
hmsquestionsList.add(hques3)
// 4
val hques4 = HMSQuestions(4, "How many landmark names can be recognized by ML Kit?",
"300", "3000+", "500", "5000+", 4)
hmsquestionsList.add(hques4)
// 5
val hques5 = HMSQuestions(5, "What is the full form of OAID?",
"Open Advertising Identifier", "Open Adverse Identity", "Open Amendment Information", "Open All Identity", 1)
hmsquestionsList.add(hques5)
// 6
val hques6 = HMSQuestions(6, "How many user addresses are allowed by Huawei Kit for user?",
"5", "8", "15", "10", 4)
hmsquestionsList.add(hques6)
// 7
val hques7 = HMSQuestions(7, "Which type of format supports Panorama Kit?",
"JPG", "JPEG", "PNG", "All of the above", 4)
hmsquestionsList.add(hques7)
// 8
val hques8 = HMSQuestions(8, "Which method is used to obtain the last requested available location.",
"getLastLocation()", "onLocationAvailability()", "onLocationResult", "None", 1)
hmsquestionsList.add(hques8)
// 9
val hques9 = HMSQuestions(9, "How many languages does the Video Editor Kit supports?",
"100", "48", "55", "78", 4)
hmsquestionsList.add(hques9)
// 10
val hques10 = HMSQuestions(10, "What are the formats supported by Audio Editor Kit?",
"MP3", "WAV and AAC", "M4A", "All of the above", 4)
hmsquestionsList.add(hques10)
return hmsquestionsList
}
}
In the activity_android.xml we can create the UI screen for questions.
XML:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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:fillViewport="true"
tools:context=".android.AndroidActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center" >
<TextView
android:id="@+id/txt_ardquestion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:text="What is Android?"
android:textColor="#363A43"
android:textSize="18sp">
</TextView>
<LinearLayout
android:id="@+id/progress_details_android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="16dp" >
<ProgressBar
android:id="@+id/progressBar_ard"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:minHeight="50dp"
android:progress="0"
android:indeterminate="false"
android:max="10">
</ProgressBar>
<TextView
android:id="@+id/txt_progress_ard"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp"
android:gravity="center"
android:textColorHint="#7A8089"
android:textSize="14sp"
tools:text="0/10">
</TextView>
</LinearLayout>
<TextView
android:id="@+id/txt_option1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="Apple">
</TextView>
<TextView
android:id="@+id/txt_option2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="Google">
</TextView>
<TextView
android:id="@+id/txt_option3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="iOS">
</TextView>
<TextView
android:id="@+id/txt_option4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="Huawei">
</TextView>
<Button
android:id="@+id/btn_ardsubmit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@color/design_default_color_primary"
android:text="Submit"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
In the activity_android_result.xml we can create the UI screen for result.
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:gravity="center"
android:background="@drawable/ardresult_bg"
android:padding="20dp"
tools:context=".android.AndroidResultActivity">
<TextView
android:id="@+id/txt_ardresult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="25dp"
android:text="Result"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="22sp">
</TextView>
<ImageView
android:id="@+id/img_ardtrophy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:src="@drawable/trophy"/>
<TextView
android:id="@+id/txt_ardcongratulations"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Hi, Congratulations!!"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="25sp">
</TextView>
<TextView
android:id="@+id/txt_ardscore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="Your Score is 5 out of 10"
android:textColor="@color/hwid_auth_button_color_red"
android:textSize="20sp">
</TextView>
<Button
android:id="@+id/btn_ardfinish"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@android:color/white"
android:text="Finish"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
In the activity_hmsactivity.xml we can create the UI screen for questions.
XML:
<?xml version="1.0" encoding="utf-8"?>
<ScrollView 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:fillViewport="true"
tools:context=".hms.HMSActivity">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp"
android:gravity="center" >
<TextView
android:id="@+id/txt_hmsquestion"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:gravity="center"
android:text="What is hms?"
android:textColor="#363A43"
android:textSize="18sp">
</TextView>
<LinearLayout
android:id="@+id/progress_details_hms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginTop="16dp" >
<ProgressBar
android:id="@+id/progressBar_hms"
style="?android:attr/progressBarStyleHorizontal"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:minHeight="50dp"
android:progress="0"
android:indeterminate="false"
android:max="10">
</ProgressBar>
<TextView
android:id="@+id/txt_progress_hms"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:padding="15dp"
android:gravity="center"
android:textColorHint="#7A8089"
android:textSize="14sp"
tools:text="0/10">
</TextView>
</LinearLayout>
<TextView
android:id="@+id/txt_option1_hms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="Apple">
</TextView>
<TextView
android:id="@+id/txt_option2_hms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="Google">
</TextView>
<TextView
android:id="@+id/txt_option3_hms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="iOS">
</TextView>
<TextView
android:id="@+id/txt_option4_hms"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@drawable/border_bg"
android:padding="15dp"
android:gravity="center"
android:textColor="#7A8089"
android:textSize="18sp"
tools:text="Huawei">
</TextView>
<Button
android:id="@+id/btn_hms_submit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="10dp"
android:background="@color/design_default_color_primary"
android:text="Submit"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
</ScrollView>
In the activity_hmsresult.xml we can create the UI screen for result.
XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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:orientation="vertical"
android:gravity="center"
android:background="@drawable/hmsresult_bg"
android:padding="20dp"
tools:context=".hms.HMSResultActivity">
<TextView
android:id="@+id/txt_hmsresult"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="25dp"
android:text="Result"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="22sp">
</TextView>
<ImageView
android:id="@+id/img_hmstrophy"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:src="@drawable/victory"/>
<TextView
android:id="@+id/txt_hmscongratulations"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:text="Congrats done HMS Quiz!!"
android:textColor="@android:color/white"
android:textStyle="bold"
android:textSize="25sp">
</TextView>
<TextView
android:id="@+id/txt_hmsscore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="15dp"
android:text="Your Score is 6 out of 10"
android:textColor="@color/black"
android:textSize="20sp">
</TextView>
<Button
android:id="@+id/btn_hmsfinish"
android:layout_width="150dp"
android:layout_height="wrap_content"
android:layout_margin="15dp"
android:background="@android:color/white"
android:text="Finish"
android:textAllCaps="false"
android:textColor="@color/white"
android:textSize="18sp"
android:textStyle="bold" />
</LinearLayout>
Demo
Tips and Tricks
1. Make sure you are already registered as Huawei developer.
2. Set minSDK version to 24 or later, otherwise you will get AndriodManifest merge issue.
3. Make sure you have added the agconnect-services.json file to app folder.
4. Make sure you have added SHA-256 fingerprint without fail.
5. Make sure all the dependencies are added properly.
Conclusion
In this article, we have learned about the basic questions on Android and HMS of Quiz application. The purpose of conducting quizzes within the business is more fun and educative to understand how the business is running straight away, test the knowledge of the user and helps to form a businessman or woman a far better person in terms of the business process. So, I will provide the series of articles on this Quiz App, in upcoming articles
I hope you have read this article. If you found it is helpful, please provide likes and comments.
Reference
Click the URL - https://www.geeksforgeeks.org/how-to-create-a-quiz-app-in-android/