I have a MVVM setup for my project and I have a simple layout with an EditText and a button and when I click the button I want to show Text which is in the EditText.
For the button when i add. this code android:onClick="#={() -> addProductViewModel.addProduct()}" it gives me an error A failure occurred while executing org.jetbrains.kotlin.gradle.internal.KaptExecution
and when I remove that like app builds okay.
Error is not so clear and I am not sure how to fix it.
here is my code
add_product.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android" >
<data class=".AddProductBinding">
<variable
name="addProductViewModel"
type="com.rao.iremind.AddProductViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<EditText
android:id="#+id/etProductName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:text="#={addProductViewModel.inputProductName}"
android:hint="Product name"
android:inputType="textPersonName"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/btn_add_product"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="160dp"
android:text="Add product"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/etProductName" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
AddProductViewModel
class AddProductViewModel (
private val repository: ProductRepository,
private val context: Context
): ViewModel(), Observable {
#Bindable
val inputProductName = MutableLiveData<String>()
fun addProduct() {
//inputProductName.value
Toast.makeText(context, " add product ", Toast.LENGTH_LONG).show()
}
override fun removeOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
}
AddProductViewModelFactory
class AddProductViewModelFactory (
private val repository: ProductRepository,
private val context: Context
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
if (modelClass.isAssignableFrom(AddProductViewModel::class.java)) {
return AddProductViewModel(repository, context) as T
}
throw IllegalArgumentException("Unknown View Model class")
}
}
Your help and suggestions are much appreciated
Thanks
R
Related
I have to implement click listener using binding and ViewModel as per clean architecture.
I have two button to select language like English and Chinese.
LanguageActivity.kt
#AndroidEntryPoint
class LanguageActivity : PBActivity(R.layout.activity_language) {
private val mViewModel:LanguageViewModel by viewModels()
private val mBinding:ActivityLanguageBinding by viewbind()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBinding.apply {
lifecycleOwner = this#LanguageActivity
viewModel = mViewModel
}
collectFlow(mViewModel.uiState){
Toast.makeText(this, it.name, Toast.LENGTH_SHORT).show()
when(it){
LanguageSelected.ENGLISH -> {
}
LanguageSelected.CHINESE -> {
}
LanguageSelected.NONE -> {
}
}
}
}
}
enum class LanguageSelected{
ENGLISH,CHINESE,NONE
}
LanguageViewModel.kt
#HiltViewModel
class LanguageViewModel #Inject constructor(
private val pbPrefs: PBPrefs
): ViewModel(){
// Backing property to avoid state updates from other classes
private val _uiState = MutableStateFlow(LanguageSelected.NONE)
// The UI collects from this StateFlow to get its state updates
val uiState: StateFlow<LanguageSelected> = _uiState
fun englishSelected()= viewModelScope.launch {
_uiState.value = LanguageSelected.ENGLISH
pbPrefs.setLanguageSelect()
}
fun urduSelected() = viewModelScope.launch {
_uiState.value = LanguageSelected.URDU
pbPrefs.setLanguageSelect()
}
}
activity_language.xml
<?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>
<variable
name="viewModel"
type="uk.co.planetbeyond.telenorbluecollar.ui.language.LanguageViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.language.LanguageActivity">
<androidx.constraintlayout.widget.Guideline
android:id="#+id/center_vertical_gl"
android:layout_width="match_parent"
android:layout_height="1dp"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.5" />
<Button
android:id="#+id/englishTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="English "
android:onClick="#{() -> viewModel.englishSelected()}"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="#id/center_vertical_gl"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Button
android:id="#+id/urduTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Urdu"
android:onClick="#{() -> viewModel.urduSelected()}"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="#id/center_vertical_gl"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
I have to select language on click of a button. What's the best approach as per clean architecture to implement
I have created an interface LanguageSelected but for that I have to created multiple methods in viewModel, As languages increase I have to create more methods in viewmodel.
How could I make it short or I mean extensible?
I write a code for a simple dice roll app but there is a problem in updating the image of an image view that bound with a binding adapter
I can't extend BaseObservable class because I already extended the ViewModel class. I need that for keeping data after the status changed. Also, I can't observe the live data inside my binding adapter because the function is static
do you know any other way?
This is my xml 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>
<variable
name="viewModel"
type="com.example.diceroller.MainActivityViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:id="#+id/imgDice"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:imageRes="#{viewModel.drawableResource}" />
<Button
android:id="#+id/btnRoll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="32dp"
android:text="Roll the dice"
android:onClick="#{() -> viewModel.onRollButtonClicked()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
and this is my kotlin code
class MainActivityViewModel : ViewModel() , Observable {
#Bindable
var drawableResource : MutableLiveData<Int> = MutableLiveData<Int>()
init {
drawableResource.value = R.drawable.dice_1
}
fun onRollButtonClicked(){
drawableResource.value = when(Random.nextInt(1,7)){
1 -> R.drawable.dice_1
2 -> R.drawable.dice_2
3 -> R.drawable.dice_3
4 -> R.drawable.dice_4
5 -> R.drawable.dice_5
else -> R.drawable.dice_6
}
}
override fun addOnPropertyChangedCallback(callback: Observable.OnPropertyChangedCallback?) {
}
override fun removeOnPropertyChangedCallback(callback:
Observable.OnPropertyChangedCallback?) {
}
}
#BindingAdapter("android:imageRes")
fun loadImage(view : View , imageRes : Int){
(view as ImageView).setImageResource(imageRes)
}
So I am developing and android app in Kotlin with coroutines and no matter what change I make, I still keep getting the message:
I/Choreographer: Skipped 59 frames! The application may be doing too much work on its main thread.
How can I get rid of it. I mean like I am only displaying nine photos... Below is my code
Model:
data class Food (
val id: String,
val name: String,
val price: String,
#Json(name = "img_url") val imgSrcUrl: String,
val type: String,
val description: String,
val average_rating: String,
val number_of_raters: String,
val special_price: String
)
data class FoodCategory(
val id: String,
val title: String,
val foods: List<Food>
)
ViewModel:
enum class NetworkStatus {LOADING, DONE, FAILED}
enum class FontFamily (#FontRes val fontRes: Int) {
POPPINS_BOLD(R.font.poppins_bold),
POPPINS(R.font.poppins)
}
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
//This is the data that is gonna be exposed to the viewmodel
//It will be submitted to a ListAdapter
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
//Used to display a progress bar for network status
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch(Dispatchers.Default) {
_status.postValue(NetworkStatus.LOADING)
try {
getUpdatedFood()
Log.i("getOverviewProducts","I am running on tread: $coroutineContext")
_status.postValue(NetworkStatus.DONE)
}catch (e: Exception) {
_status.postValue(NetworkStatus.FAILED)
}
}
}
private suspend fun getUpdatedFood() {
//withContext(Dispatchers.Default) {
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
_foodCategory.postValue(foodCategory)
//}
}
}
Repository:
class FoodRepository {
suspend fun getRecommendedFood(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getRecommended","I am running on tread: $coroutineContext")
return#withContext ProductApi.retrofitService.getRecommended(limiter)
}
suspend fun getSpecials(limiter: String) = withContext(Dispatchers.IO) {
Log.i("Resp-getSpecials","I am running on tread: $coroutineContext")
return#withContext ProductApi.retrofitService.getSpecials(limiter)
}
}
BindingAdapters:
//Load image using Glide (in Food item recycleview)
#BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView , imgUrl: String?) {
imgUrl?.let {
val imgUri = imgUrl.toUri().buildUpon().scheme("http").build()
Glide.with(imgView.context)
.load(imgUri)
.apply(
RequestOptions()
.placeholder(R.drawable.loading_animation)
.error(R.drawable.ic_broken_image))
.into(imgView)
}
}
//set raters count (in Food item recycleview)
#BindingAdapter("ratersCount")
fun bindText(txtView: TextView, number_of_raters: String?) {
number_of_raters?.let {
val ratersCount = "(${number_of_raters})"
txtView.text = ratersCount
}
}
//update the progressbar visibilty (in outer-parent recycleview)
#BindingAdapter("updateStatus")
fun ProgressBar.updateStatus(status: NetworkStatus?) {
visibility = when (status) {
NetworkStatus.LOADING -> View.VISIBLE
NetworkStatus.DONE -> View.GONE
else -> View.GONE
}
}
//Hide or view an imageview based in the network Status. When network Error, an error image
//will show (in outer-parent recycleview)
#BindingAdapter("setNoInternet")
fun ImageView.setNoInternet(status: NetworkStatus?) {
when(status) {
NetworkStatus.LOADING -> {
visibility = View.GONE
}
NetworkStatus.DONE -> {
visibility = View.GONE
}
NetworkStatus.FAILED -> {
visibility = View.VISIBLE
setImageResource(R.drawable.ic_connection_error)
}
}
}
//Submit the list of FoodCatergory item to the outer-parent recycleview
#BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, data: List<FoodCategory>?) {
(recyclerView.adapter as FoodCategoryAdapter).submitList(data)
}
//Submit list the the Food item recyclew view (child recycleView)
#BindingAdapter("setProducts")
fun RecyclerView.setProducts(foods: List<Food>?) {
if (foods != null) {
val foodAdapter = FoodItemAdapter()
foodAdapter.submitList(foods)
adapter = foodAdapter
}
}
I have a Recycleview of Food Item and a Recycleview Pool of FoodCategory. If I comment out
_foodCategory.postValue(foodCategory)
in ViewModel: getUpdatedFood() than I do not get the message. However, when I submit the list to the outer recycleview (The one the hold the viewpool), than I get this answer. Please help. I been stuck on it for a while tryna get rid of that message.
Thank you..
Updated
Below is the adapeters and its view holders
FoodItem layout
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<import type="android.view.View"/>
<variable
name="foodItem"
type="com.example.e_commerceapp.models.Food"/>
<variable
name="font"
type="com.example.e_commerceapp.products.overview.FontFamily"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/child_item_main_layout"
android:background="#drawable/search_background"
android:layout_marginTop="10dp"
android:layout_marginStart="10dp"
android:layout_marginBottom="10dp"
android:layout_width="150dp"
android:layout_height="250dp">
<ImageView
android:layout_width="120dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:id="#+id/burger_image"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_height="160dp"
/>
<!-- app:imageUrl="#{foodItem.imgSrcUrl}"-->
<TextView
android:layout_width="match_parent"
android:layout_height="34dp"
android:layout_marginStart="5dp"
android:id="#+id/burger_title"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginEnd="5dp"
android:singleLine="true"
android:textColor="#B4000000"
app:layout_constraintTop_toBottomOf="#id/burger_image"
android:text="#{foodItem.name}"
android:textSize="12sp"
android:fontFamily="#font/poppins"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintTop_toBottomOf="#id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center"
android:id="#+id/burger_price"
android:layout_marginStart="5dp"
app:layout_constraintStart_toEndOf="#id/special_price"
android:textColor="#D0000000"/>
<!-- app:price="#{foodItem.price}"-->
<!-- app:specialPrice="#{foodItem.special_price}"-->
<!-- app:fontRes="#{foodItem.special ? font.POPPINS : font.POPPINS_BOLD}"-->
<TextView
android:layout_width="wrap_content"
android:layout_height="35dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/burger_title"
android:layout_marginEnd="5dp"
android:gravity="center_vertical"
android:layout_marginStart="5dp"
android:id="#+id/special_price"
android:textColor="#D0000000"
android:visibility="gone"
android:textStyle="normal"
android:fontFamily="#font/poppins_bold"/>
<!-- app:setSpecialPrice="#{foodItem.special_price}"-->
<ImageView
android:layout_width="15dp"
android:layout_height="15dp"
app:layout_constraintTop_toBottomOf="#id/burger_price"
android:src="#drawable/ic_baseline_star_24"
android:visibility="#{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="#+id/rating_star"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="5dp"
android:layout_marginBottom="10dp"
app:layout_constraintBottom_toBottomOf="parent"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:layout_marginStart="5dp"
android:gravity="center"
android:textSize="12sp"
android:visibility="#{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:id="#+id/rating_count"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/burger_price"
android:text="#{foodItem.average_rating}"
android:layout_marginBottom="10dp"
app:layout_constraintStart_toEndOf="#id/rating_star"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="15dp"
android:id="#+id/number_of_raters"
android:textSize="12sp"
android:visibility="#{foodItem.hasRating ? View.GONE : View.VISIBLE}"
android:layout_marginStart="2dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toEndOf="#id/rating_count"
app:ratersCount="#{foodItem.number_of_raters}"
android:layout_marginBottom="10dp"
app:layout_constraintTop_toBottomOf="#id/burger_price"/>
<ImageView android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginEnd="10dp"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodItem Adapter
class FoodItemAdapter: ListAdapter<Food ,
FoodItemAdapter.ItemFoodViewHolder>(DiffCallback) {
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): ItemFoodViewHolder {
return ItemFoodViewHolder(
FoodItemBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: ItemFoodViewHolder , position: Int) {
val currentFood = getItem(position)
holder.bind(currentFood)
}
class ItemFoodViewHolder(private var binding: FoodItemBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(food: Food) {
binding.foodItem = food
binding.executePendingBindings()
}
}
object DiffCallback: DiffUtil.ItemCallback<Food>() {
override fun areItemsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: Food , newItem: Food): Boolean {
return oldItem.id == newItem.id
}
}
}
FoodCategory layout
<?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>
<variable
name="foodCategory"
type="com.example.e_commerceapp.models.FoodCategory"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#fff"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:id="#+id/category_title"
android:layout_marginTop="16dp"
android:text="#{foodCategory.title}"
android:textColor="#2B2A2A"
android:fontFamily="#font/poppins_bold"
android:textSize="16sp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/nestedRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dp"
app:setProducts="#{foodCategory.foods}"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/category_title"
tools:itemCount="4"
tools:listitem="#layout/food_item"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
FoodCategory Adapter
class FoodCategoryAdapter: ListAdapter<FoodCategory,
FoodCategoryAdapter.CategoryFoodViewHolder>(Companion) {
private val viewPool = RecyclerView.RecycledViewPool()
override fun onCreateViewHolder(parent: ViewGroup , viewType: Int): CategoryFoodViewHolder {
return CategoryFoodViewHolder(FoodCategoryBinding.inflate(LayoutInflater.from(parent.context),
parent, false))
}
override fun onBindViewHolder(holder: CategoryFoodViewHolder , position: Int) {
val currentFoodCategory = getItem(position)
holder.bind(currentFoodCategory)
}
inner class CategoryFoodViewHolder(private var binding: FoodCategoryBinding): RecyclerView.ViewHolder(binding.root) {
fun bind(currentFoodCategory: FoodCategory?) {
binding.foodCategory = currentFoodCategory
binding.nestedRecyclerView.setRecycledViewPool(viewPool)
binding.executePendingBindings()
}
}
companion object: DiffUtil.ItemCallback<FoodCategory>() {
override fun areItemsTheSame(oldItem: FoodCategory , newItem: FoodCategory): Boolean {
return oldItem === newItem
}
override fun areContentsTheSame(oldItem: FoodCategory, newItem: FoodCategory): Boolean {
return oldItem.id == newItem.id
}
}
}
The parent recycleView
<?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"
tools:context=".products.overview.FoodOverviewFragment">
<data>
<variable
name="foodOverview"
type="com.example.e_commerceapp.products.overview.FoodOverviewViewModel"/>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:background="#color/grey"
android:layout_width="match_parent"
android:layout_height="match_parent">
<RelativeLayout
android:layout_width="match_parent"
android:id="#+id/relative_layout"
android:layout_height="105dp"
android:elevation="8dp"
android:layout_marginBottom="5dp"
android:background="#fff"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent">
<ImageView
android:layout_width="200dp"
android:layout_marginTop="10dp"
android:layout_height="35dp"
android:id="#+id/logo_and_name"
android:src="#drawable/compony_logo_and_name"
android:layout_alignParentStart="true"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="35dp"
android:layout_marginTop="10dp"
android:id="#+id/notifications"
android:src="#drawable/ic_baseline_notifications_24"
android:layout_alignParentEnd="true"
android:paddingEnd="20dp"
android:paddingStart="20dp"/>
<TextView
android:layout_width="match_parent"
android:id="#+id/search"
android:layout_marginTop="10dp"
android:layout_marginStart="20dp"
android:layout_marginEnd="20dp"
android:background="#drawable/search_background"
android:layout_below="#id/logo_and_name"
android:gravity="center_vertical"
android:layout_height="match_parent"
android:layout_marginBottom="10dp"
android:paddingEnd="10dp"
android:paddingStart="10dp"
android:text="#string/search_text"
tools:ignore="RtlSymmetry"
app:drawableEndCompat="#drawable/ic_baseline_search_24"/>
</RelativeLayout>
<ProgressBar
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:updateStatus="#{foodOverview.status}"
app:layout_constraintTop_toBottomOf="#id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="#+id/progressbar"/>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="#id/relative_layout"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
android:id="#+id/noInternetImage"/>
<androidx.recyclerview.widget.RecyclerView
android:layout_width="0dp"
android:id="#+id/foodCategory"
android:clipToPadding="false"
tools:itemCount="4"
tools:listitem="#layout/food_category"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_height="0dp"
app:listData="#{foodOverview.foodCategory}"
app:layout_constraintTop_toBottomOf="#id/relative_layout"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
The skipping frames likely has nothing to do with the code you posted: it sounds like a misconfiguration of RecyclerViews / Adapters to me. You'll need to post that code for it be more clear though.
However, even though what you posted likely isn't the culprit you can still optimize the coroutines code you have:
class FoodOverviewViewModel(private val foodRepository: FoodRepository): ViewModel() {
private lateinit var foodProducts: List<Food>
private val _foodCategory = MutableLiveData<List<FoodCategory>>()
val foodCategory: LiveData<List<FoodCategory>>
get() = _foodCategory
private val _status = MutableLiveData<NetworkStatus>()
val status: LiveData<NetworkStatus>
get() = _status
init {
getOverviewProducts()
}
private fun getOverviewProducts() {
viewModelScope.launch { // <------- Don't apply a custom scope here
_status.value = NetworkStatus.LOADING // <--- Don't call "postValue" here
try {
val food = getUpdatedFood() // <------ This is already using a background dispatcher
_foodCategory.value = food // <------- Emit this value here
_status.value = NetworkStatus.DONE
} catch (e: Exception) {
_status.value = NetworkStatus.FAILED
}
}
}
private suspend fun getUpdatedFood(): List<FoodCategory> { // <---- Return a value here
val limiter = 6 //Number of items I want to get from the server
val foodCategory = arrayListOf<FoodCategory>()
Log.i("getUpdatedFood","I am running on tread: $coroutineContext")
val getRecommended = foodRepository.getRecommendedFood(limiter.toString())
foodCategory += FoodCategory(id = 0.toString(), title = "Recommended for you", foods = getRecommended)
val getSpecials = foodRepository.getSpecials(limiter.toString())
foodCategory += FoodCategory(id = 1.toString(), title = "Specials", foods = getSpecials)
return foodCategories
}
}
The key ideas here:
Use viewModelScope.launch { ... } rather than applying a scope. You want everything in there that is not a coroutine to run on the main thread. See, for example, https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe
Return a value from getUpdatedFood so you can just update the _foodCategory value on the main thread in the viewModelScope
I have an exisitng code which I integrate with Live data by using retrofit.
Now if I want to integrate databinding, where are all changes to be done to make the code looks perfect?
Here is my code.
activity_main.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:background="#000000"
android:layout_height="match_parent"
tools:context=".MainActivity">
<LinearLayout
android:id="#+id/linearLayout2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="Android Versions"
android:textColor="#ffffff"
android:textSize="20sp" />
<android.support.v7.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
items.xml:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.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:background="#445566"
android:layout_margin="5dp"
android:layout_height="wrap_content">
<TextView
android:id="#+id/tvFname"
android:padding="5dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="top|start"
android:layout_marginTop="8dp"
android:textColor="#ffffff"
android:layout_weight="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
<TextView
android:id="#+id/tvLname"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center|start"
android:padding="5dp"
android:layout_marginTop="10dp"
android:layout_weight="1"
android:ellipsize="end"
android:textColor="#ffffff"
android:maxLines="5"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/tvFname"/>
</android.support.constraint.ConstraintLayout>
Here is view model class which I integrated
class AndroidViewModel:ViewModel() {
private val mService = RetrofitService()
fun getAndroidData():MutableLiveData<List<AndroidData>>?{
return mService.loadAndroidData()
}
}
Pojo class generated is just a simple one:
data class AndroidData (val name:String, val apiLevel:String)
Service integration:
class RetrofitService {
val liveUserResponse:MutableLiveData<List<AndroidData>> = MutableLiveData()
companion object Factory {
var gson = GsonBuilder().setLenient().create()
fun create(): ApiInterface {
Log.e("retrofit","create")
val retrofit = Retrofit.Builder()
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.baseUrl("https://learn2crack-json.herokuapp.com/api/")
.build()
return retrofit.create(ApiInterface::class.java)
}
}
fun loadAndroidData(): MutableLiveData<List<AndroidData>>? {
Log.e("loadAndroidData","yes")
val retrofitCall = create().getAndroid()
retrofitCall.enqueue(object : Callback<List<AndroidData>> {
override fun onFailure(call: Call<List<AndroidData>>, t: Throwable?) {
Log.e("on Failure :", "retrofit error")
}
override fun onResponse(call: Call<List<AndroidData>>, response: retrofit2.Response<List<AndroidData>>) {
val list = response.body()
for (i in list.orEmpty()){
Log.e("on response 1:", i.name)
}
liveUserResponse.value = list
Log.e("hasActiveObservers 1", liveUserResponse.hasActiveObservers().toString()+" check")
Log.e("on response 2 :", liveUserResponse.toString()+" check")
}
})
return liveUserResponse
}
Main Activity:
class MainActivity : AppCompatActivity(){
private lateinit var linearLayoutManager: LinearLayoutManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
linearLayoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(true)
getAndroidVersion()
}
private fun getAndroidVersion() {
Log.e("getAndroidVersion", "yes")
val mAndroidViewModel = ViewModelProviders.of(this#MainActivity).get(AndroidViewModel::class.java)
mAndroidViewModel.getAndroidData()?.observe(this, Observer<List<AndroidData>> { androidList ->
Log.e("list", androidList?.size.toString())
recyclerView.adapter = EmpAdapter(this#MainActivity, androidList as ArrayList<AndroidData>, object :
ItemClickListener {
override fun onItemClick(pos: Int, name:String) {
Toast.makeText(applicationContext, "item "+pos+ "clicked"+ name, Toast.LENGTH_LONG).show()
}
})
})
}
}
Adapter class:
class EmpAdapter(
var context: MainActivity,
var mEmpList: ArrayList<AndroidData>,
private val itemClick:ItemClickListener
) :
RecyclerView.Adapter<EmpAdapter.EmpHolder>() {
override fun getItemCount(): Int {
return mEmpList.size
}
companion object {
var mItemClickListener : ItemClickListener? = null
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EmpHolder {
val view = LayoutInflater.from(context).inflate(R.layout.items, parent, false)
return EmpHolder(view)
}
override fun onBindViewHolder(holder: EmpHolder, position: Int) {
mItemClickListener = itemClick
holder.tvFname?.text = mEmpList[position].name
holder.tvLname?.text = mEmpList[position].apiLevel
RxView.clicks(holder.mView).subscribe {
mItemClickListener!!.onItemClick(position,mEmpList[position].name)
}
}
class EmpHolder(view: View) : RecyclerView.ViewHolder(view) {
val tvFname = view.tvFname
val tvLname = view.tvLname
val mView = view
}
}
interfaces for retrofit:
interface ApiInterface {
#GET("android")
fun getAndroid(): Call<List<AndroidData>>
}
Now my question is, if I integrate tag and in xml, which variable i need to specify for textview to integrate to connect for textview in adapter? Kindly guide me where are all I need to change to apply, if I use live data
First of all, i strongly recommend to use a Retrofit Adapter that converts the response into LiveData automatically for you. See this example
https://github.com/yasiralijaved/android-architecture-components/blob/master/component_http/src/main/java/com/yasiralijaved/android/arc/component/http/BackendService.java
Secondly there are detailed tutorials out there which can surely help you how to implement Data Bindings in RecyclerView Adapter. A few tutorials are:
https://medium.com/androiddevelopers/android-data-binding-recyclerview-db7c40d9f0e4
https://android.jlelse.eu/how-to-bind-a-list-of-items-to-a-recyclerview-with-android-data-binding-1bd08b4796b4
Do let me know if it is still not clear and i will share some more details.
Happy coding!!
I am using databinding in my application but button is not working. where am i making mistake in this code. i tried many solution but no luck. but if make button in activity_login.xml then button click works. i think i am not able to pass the view model to the included view.
Here is my code
activity_login.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:bind="http://schemas.android.com/apk/res-auto">
<data>
<variable
name ="loginViewModel"
type="com.innowi.checoutrestaurantdashboard.view.main.MainViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="#+id/login_constraint_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#drawable/login_background_burgerstack"
android:isScrollContainer="false"
android:paddingEnd="32dp"
android:paddingStart="32dp"
tools:context=".view.main.LoginActivity">
<include
android:id="#+id/login_layout"
layout="#layout/layout_login"
android:layout_width="0dp"
android:layout_height="0dp"
bind:loginViewModel="#{loginViewModel}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"/>
</android.support.constraint.ConstraintLayout>
</layout>
layout_login.xml
<?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">
<data>
<variable
name ="loginViewModel"
type="com.innowi.checoutrestaurantdashboard.view.main.MainViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
style="#style/Login"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:isScrollContainer="false"
android:padding="32dp">
<Button
android:id="#+id/login_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
android:background="#drawable/button_login_drawable"
android:enabled="false"
android:gravity="center"
android:paddingBottom="16dp"
android:paddingTop="16dp"
android:text="#string/button_sign_in"
android:textColor="#color/colorWhite"
android:textSize="24sp"
android:onClick="#{loginViewModel::onLoginButtonClick}"
app:layout_constraintLeft_toLeftOf="#+id/login_username"
app:layout_constraintRight_toRightOf="#+id/login_username"
app:layout_constraintTop_toBottomOf="#+id/login_password" />
.
.
.
.
</android.support.constraint.ConstraintLayout>
</layout>
MainViewModel.kt
class MainViewModel #Inject constructor(
private val loginRepository: LoginRepository
) : BaseViewModel() {
fun onLoginButtonClick(view : View){
performLogin()
}
.
.
.
}
LoginActivity.kt
class LoginActivity : AppBaseActivity() {
private lateinit var loginViewBinding: ActivityLoginBinding
private lateinit var viewModel: MainViewModel
private val TAG = LoginActivity::class.simpleName
override fun initViewModel(viewModelProvider: ViewModelProvider): BaseViewModel? {
viewModel = viewModelProvider.get(MainViewModel::class.java)
return viewModel
}
override fun render(state: ViewState) {
when (state) {
is LoadingState -> {
// loading
val loading = state.loading
Log.d(TAG,"Loading State")
}
is DefaultState -> {
// render Data
val data = state.data
Log.d(TAG,"Data State")
}
is ErrorState -> {
// show error
val error = state.error
Log.d(TAG,"Loading State")
}
}
}
override fun setContentView() {
loginViewBinding = DataBindingUtil.setContentView(this, R.layout.activity_login)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
loginViewBinding.loginViewModel = viewModel
loginViewBinding.executePendingBindings()
}
}