MVVM with API calling and Data binding

MVVM with API calling and Data binding

 MVVM with API calling using retrofit and other things like data binding, Coroutines and all



sample demo which I have created sample using AndroidHiveJSON

https://drive.google.com/file/d/1ejbuQ6cw0WSnsgCl9NsFeugKh0HzJwiK/view?usp=sharing



sample demo with GIT

https://drive.google.com/file/d/1RQLsNJGTO0th61Tmzxdyn5V5oS1xMeDb/view?usp=sharing


MVP Android

MVP Android

 MVP Android

1) SearchInteractor

    package com.hexabrain.aeon.ui.activities.Search


import android.content.Context
import android.util.Log
import com.hexabrain.aeon.api.APIClient
import com.hexabrain.aeon.model.ItemMain.VerticalGrid.New.AllChannelMain
import com.hexabrain.aeon.utils.AppPreferences
import com.hexabrain.aeon.utils.addTo
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.internal.schedulers.IoScheduler

class SearchInteractor {

interface OnLoginFinishedListener {
fun onError(noData:Boolean)
fun rowItemisLoading(isloading :Boolean)
fun showProgress()
fun hideProgress()
fun totalPages(totalPages: Int)
fun onSuccess(
searchMain: AllChannelMain,
totalSize: Int?
)
}
private fun createArrayList(): List<String> = (1..10).map { "Item $it" }

fun getSearchData(
requireContext: Context,
listener: OnLoginFinishedListener,
newValue: String,
page: Int?,
disposables: CompositeDisposable,
totalSize: Int?
) {
val token: String? = AppPreferences.from(requireContext)?.token

Log.e("=====Searchquery","value: ${newValue},, page: ${page}")

val header = HashMap<String, String>()
header["Authorization"] = "$token"
header["Content-Type"] = "application/json"

listener.showProgress()
APIClient.api.getSearchVideosInSearchPage(header, newValue, page)
.observeOn(AndroidSchedulers.mainThread())
.subscribeOn(IoScheduler())
.subscribe(
{
listener.totalPages(it?.totalPages!!)
// listener.totalPages(4)
if (it.videos?.isNullOrEmpty()?.not()!!) {
listener.onError(false)
listener.onSuccess(it,totalSize)
}else{
listener.onError(true)
}
listener.hideProgress()
listener.rowItemisLoading(false)

},
{
listener.hideProgress()
listener.onError(true)
listener.rowItemisLoading(false)
listener.hideProgress()
Log.e("SearchFragment", "Error:- ${it.message}")
}
)
.addTo(disposables)
}
}


2) SearchPresenter


package com.hexabrain.aeon.ui.activities.Search

import android.content.Context
import com.hexabrain.aeon.model.ItemMain.VerticalGrid.New.AllChannelMain
import io.reactivex.disposables.CompositeDisposable

class SearchPresenter(var searchView: SearchView?, val searchInteractor: SearchInteractor) :
SearchInteractor.OnLoginFinishedListener {

fun getSearchData(
requireContext: Context,
newValue: String,
page: Int?,
disposables: CompositeDisposable,
totalSize: Int?
) {
searchView?.showProgress()
searchInteractor.getSearchData(requireContext, this,newValue,page,disposables,totalSize)
}

fun onDestroy() {
searchView = null
}

override fun onError(noData:Boolean) {
searchView?.apply {
onError(noData)
hideProgress()
}
}

override fun rowItemisLoading(isloading: Boolean) {
searchView?.apply {
itemIsLoading(isloading)
}
}

override fun showProgress() {
searchView?.apply {
showProgress()
}
}

override fun hideProgress() {
searchView?.apply {
hideProgress()
}
}

override fun totalPages(totalPages: Int) {
searchView?.apply {
totalPages(totalPages)
}
}

override fun onSuccess(
searchMain: AllChannelMain,
totalSize: Int?
) {
searchView?.successs(searchMain,totalSize)
}
}

3) SearchView

package com.hexabrain.aeon.ui.activities.Search

import com.hexabrain.aeon.model.ItemMain.VerticalGrid.New.AllChannelMain

interface SearchView {
fun showProgress()
fun hideProgress()
fun onError(noData:Boolean)
fun itemIsLoading(isloading: Boolean)
fun totalPages(pages: Int)
fun successs(searchMain: AllChannelMain,totalSize: Int?)
}
4) MyVideoFragment
package com.hexabrain.aeon.ui.activities

import android.os.Bundle
import androidx.leanback.app.RowsSupportFragment
import androidx.leanback.widget.*
import com.hexabrain.aeon.cardPresenter.SideInfoCardPresenter
import com.hexabrain.aeon.model.ItemMain.VerticalGrid.New.AllChannelMain
import com.hexabrain.aeon.model.ItemMain.VerticalGrid.New.AllChannelVideo
import com.hexabrain.aeon.ui.activities.Search.SearchInteractor
import com.hexabrain.aeon.ui.activities.Search.SearchPresenter
import com.hexabrain.aeon.ui.activities.Search.SearchView
import io.reactivex.disposables.CompositeDisposable
import okhttp3.OkHttpClient
import okhttp3.Response


class MyVideoFragment(
val onProgressbarVisibilityListener: (isStart: Boolean) -> Unit,
val onItemSelect: (seriesItem: AllChannelVideo, pos: Int, itemIndex: Int) -> Unit,
val onItemClick: (clickedSeries: AllChannelVideo, selectedIndex: Int) -> Unit,
val error: (error: Boolean, mainError: Boolean) -> Unit
) : RowsSupportFragment(),SearchView {

private val TAG = "MainFragment"
private val disposables = CompositeDisposable()
private var categoryAdapter: ArrayObjectAdapter? = null
private var categoryItemAdapter: ArrayObjectAdapter? = null
private val myVideosItemCount:Int = 10
private val presenter = SearchPresenter(this, SearchInteractor())

override fun onDestroy() {
super.onDestroy()
presenter.onDestroy()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val aa = ListRowPresenter(FocusHighlight.ZOOM_FACTOR_MEDIUM, false)
aa.shadowEnabled = false
aa.selectEffectEnabled = false
categoryAdapter = ArrayObjectAdapter(aa)
onProgressbarVisibilityListener.invoke(true)

presenter.getSearchData(requireContext(), "", 1, disposables, 0)

setOnItemViewSelectedListener { itemViewHolder, item, rowViewHolder, row ->
val rowPos = categoryAdapter?.indexOf(row) ?: 0
if (item is AllChannelVideo) {
onItemSelect.invoke(item, rowPos, categoryItemAdapter?.indexOf(item) ?: 0)
}
}

setOnItemViewClickedListener { itemViewHolder, item, rowViewHolder, row ->
if (item is AllChannelVideo) {

val listRow = row as ListRow
val currentRowAdapter = listRow.adapter as ArrayObjectAdapter
val selectedIndex = categoryItemAdapter?.indexOf(item) ?: 0

onItemClick.invoke(item, selectedIndex)
}
}
}

override fun showProgress() {
onProgressbarVisibilityListener.invoke(true)
}

override fun hideProgress() {
onProgressbarVisibilityListener.invoke(false)
}

override fun onError(noData: Boolean) {
error.invoke(noData, false)
}

override fun itemIsLoading(isloading: Boolean) {

}

override fun totalPages(pages: Int) {

}

override fun successs(searchMain: AllChannelMain, totalSize: Int?) {
var myList: MutableList<AllChannelVideo> = mutableListOf()

error.invoke(false, false)

val lastItem = AllChannelVideo()
lastItem?.videoUrl = "allchannel"
lastItem?.date = ""
lastItem?.name = "SEE ALL"
lastItem?.small_icon = ""
lastItem?.thumb = ""
lastItem?.type = "redirect"

if (searchMain?.videos?.size!! >= myVideosItemCount){
myList = searchMain?.videos?.take(myVideosItemCount)?.toMutableList()!!
myList?.add(lastItem)
}else{
myList = searchMain?.videos!!
}

val headerItem = HeaderItem("My Videos")

categoryItemAdapter = ArrayObjectAdapter(context?.let {
SideInfoCardPresenter(it)
})

if (myList?.isNullOrEmpty()?.not()!!) {
for (singleSeries in myList.indices) {
categoryItemAdapter?.add(myList!![singleSeries])
}
}

categoryAdapter?.add(ListRow(headerItem, categoryItemAdapter))


error.invoke(false, false)

onProgressbarVisibilityListener.invoke(false)
adapter = categoryAdapter
}
}

Alert Dialog from different class

Alert Dialog from different class
class AlertDialog(context: Context, message: String ,onPositive: () -> Unit) {

    private val dialog: AlertDialog

    init {
        dialog = AlertDialog.Builder(context)
            .setTitle(R.string.app_name)
            .setMessage(message)
            .setPositiveButton("Yes") { dialog, _ ->                dialog.dismiss()
                onPositive.invoke()
            }            .setNegativeButton("No") { dialog, _ ->                dialog.dismiss()
            }            .setCancelable(false)
            .create()
    }

    fun show() {
        dialog.show()
    }
}


TO access

AlertDialog(this, "Are you sure you want to exit?") {    finish()
}.show()

Intent with companion Object

Intent with companion Object
companion object {
    fun launchNewActivity(activity: Activity) {
        val intent = Intent(activity, LauncherActivity::class.java)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK        ActivityCompat.startActivity(
            activity,
            intent,
            null        )
    }
}

To access this activity call from Other activity


LauncherActivity.launchNewActivity(this)

Shared preference in Different Class

Shared preference in Different Class

Shared preference

-------------------

class AppPreferences private constructor(context: Context) {

    private val preferences = context.getSharedPreferences(PreferencesName, Context.MODE_PRIVATE)

    fun clearAll() {
        preferences.edit().clear().apply()
    }

    var status: Boolean
        get() {
            return preferences.getBoolean(Status, true)
        }
        set(value) {
            preferences.edit().putBoolean(Status, value).apply()
        }

    var isLoggedIn: Boolean
        get() {
            return preferences.getBoolean(LoggedIn, false)
        }
        set(value) {
            preferences.edit().putBoolean(LoggedIn, value).apply()
        }

    var token: String?
        get() {
            return preferences.getString(Token, null)
        }
        set(value) {
            preferences.edit().putString(Token, value).apply()
        }

    var user_display_name: String?
        get() {
            return preferences.getString(UserDisplayName, null)
        }
        set(value) {
            preferences.edit().putString(UserDisplayName, value).apply()
        }

     var userFirstName: String?
        get() {
            return preferences.getString(UserNickName, null)
        }
        set(value) {
            preferences.edit().putString(UserNickName, value).apply()
        }

    companion object {
        const val PreferencesName = "App"
        const val DefaultPreferredVideoHeight = 720

        private const val Status = "status"

        private const val Token = "token"
        private const val UserDisplayName = "user_display_name"
        private const val LoggedIn = "Loggedin"
        private const val UserNickName = "user_nicename"

        fun from(context: Context?) = context?.let { AppPreferences(it) }
    }
}



To Store Data into SharedPreference

---------------------------------

AppPreferences.from(this)?.apply {
if (!tokenValue.isEmpty()){
token = tokenValue
isLoggedIn = true
SubscriptionPreferences.from(this@LoginActivity)?.isSubscribed = true
}
status = Status
                        }


To get Value From sharedpreference

-------------------------------------

AppPreferences.from(this)?.isLoggedIn

API Calling

API Calling

API Calling 


class APIClient {
companion object {
val BASE_URL = "baseURL"

val client = OkHttpClient.Builder()
.connectTimeout(100, TimeUnit.SECONDS)
.readTimeout(100, TimeUnit.SECONDS)
.writeTimeout(100, TimeUnit.SECONDS)
.build()

val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create())
.baseUrl(BASE_URL).client(client).build()

val api = retrofit.create(RetrofitApi::class.java)
}
}




Interface for API


interface RetrofitApi {

    @GET(BuildConfig.categories)
    fun getCategories(): Observable<ModelClass>


    @POST(BuildConfig.authurl)
    fun Login(@Query("username") username: String,@Query("password") password: String): Observable<Loginmodel>

}


ADD MODEL class from http://www.jsonschema2pojo.org/



Call API From Activity

// Calling one API and will get whole data and parse manually
private val disposables = CompositeDisposable()
fun GetRMDCategories() { APIClient.api.getCategories() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(IoScheduler()) .subscribe( { Log.e(TAG, "GetRMDCategories: ${it} \n\n") val Response = it.string() // val Response = loadJSONFromAsset() if (Response != null) { // parseJSON(Response) displayData(Response) } }, { Log.i(TAG, "Error while GetRMDCategories: ${it.message}") } ).addTo(disposables) }





in companion object

companion object {
    fun Disposable.addTo(compositeDisposable: CompositeDisposable): Disposable =
        apply { compositeDisposable.add(this) }}

Fill data inTo Adapter

private fun displayData(Response: String) {
    ProgressBar.visibility = View.GONE    CategoryListWithItems = JsonParser().parseCategoryJson(Response)
    rv_parent.adapter =
        CategoriesAdapter(CategoryListWithItems,
            onAudioButtonClicked = { position1, audio, audioplaylist, videoDetails, playlist ->                progressbar_bottom.visibility = View.VISIBLE

                showCustomDialog(position1, audio, audioplaylist, videoDetails, playlist)
            })
}



Adapter

class CategoriesAdapter(
    private val videoItems: ArrayList<RMD_CategoryListWithItems>,
    private val onAudioButtonClicked: (position: Int, audio: AudioModel, audioPlaylist: java.util.ArrayList<AudioModel>, videoDetails: RMD_CategoryItems, playlist: java.util.ArrayList<String>) -> Unit
) : RecyclerView.Adapter<RMD_CategoriesAdapter.ViewHolder>() {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        val v =
            LayoutInflater.from(parent.context).inflate(R.layout.item_video_category, parent, false)
        return ViewHolder(v)
    }

    override fun getItemCount(): Int {
        return videoItems.size    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        notifyDataSetChanged()
        super.onAttachedToRecyclerView(recyclerView)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val category = videoItems.get(position)
        holder.recyclerView.apply {            layoutManager =
                LinearLayoutManager(holder.recyclerView.context, RecyclerView.HORIZONTAL, false)
            holder.textView.text = category.title_name            adapter = CategoryItemsAdapter(
                category.categoryItems,
                category.category_playlist,
                context,
                category.audioPlayList,
                onAudioClick = { position1, audio, audioplaylist, videoDetails,playlist->                    onAudioButtonClicked.invoke(
                        position1,
                        audio,
                        audioplaylist,videoDetails,playlist
                    )
                }            )
        }    }

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val recyclerView: RecyclerView = itemView.rv_child as RecyclerView
        val textView: TextView = itemView.textView
    }

}