I set up BindingAdpater at recyclerview items. But when I get from data, it doesn't not work.
as you see below, myItem is working. But not working at items that is in parameter. I don't understand why items(in parameter) is not working.
BindingAdapter
#BindingAdapter("app:items")
fun setItems(view: RecyclerView, items: List<LaunchListQuery.Launch>?) {
var myItem = arrayListOf<LaunchListQuery.Launch?>()
myItem.add(LaunchListQuery.Launch("","Id : "+1,"Site : "+1,null))
myItem.add(LaunchListQuery.Launch("","Id : "+2,"Site : "+2,null))
myItem.add(LaunchListQuery.Launch("","Id : "+3,"Site : "+3,null))
(view.adapter as MainAdapter).submitList(items)
}
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:bind="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewmodel"
type="com.haii.graphqldemo.main.MainViewModel"/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:id="#+id/main_layout">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:items="#{viewmodel.items}"/>
</LinearLayout>
</layout>
ViewModel
class MainViewModel #Inject constructor(
private val repository: DefaultRepository
) : ViewModel() {
private var _items :MutableLiveData<List<LaunchListQuery.Launch?>> = MutableLiveData()
val items : LiveData<List<LaunchListQuery.Launch?>> = _items
fun getItems(){
viewModelScope.launch {
val items = repository.getLaunchList()
_items.value = items.launches
}
}
}
I solved this by adding
onCreate at Activity
binding.lifecycleOwner = this
onActivityCreated at Fragment
binding.lifecycleOwner = this.viewLifecycleOwner
Related
I am struck at the data binding part. I have a simple app where I am using mvvm pattern to show a list. But the problem is how to bind recycler view with the id recycler_view with activity
// data class
data class MovieItem(
val imageUrl: String,
val name: String
)
// repository
class MovieRepository(private val api: Api) {
suspend fun getAllMovies() = api.getMovieList()
}
// view Model
class MovieViewModel(private val repository: MovieRepository):ViewModel() {
var listMovie = MutableLiveData<List<MovieItem>>()
var job: Job? = null
fun makeApiCall(){
job= CoroutineScope(Dispatchers.IO).launch {
var response = repository.getAllMovies()
withContext(Dispatchers.Main){
if (response != null){
listMovie.postValue(response.body())
}
}
}
}
}
// adapter
class MovieItemAdapter:RecyclerView.Adapter<MovieItemAdapter.MovieViewHolder>() {
var listMovie = mutableListOf<MovieItem>()
private val context:Context ? = null
fun setMovieItem(data : List<MovieItem>){
this.listMovie = data.toMutableList()
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val binding = DataBindingUtil.inflate<MovieItemBinding>(LayoutInflater.from(parent.context),
R.layout.movie_item,parent,
false)
return MovieViewHolder(binding)
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) =
override fun getItemCount(): Int {
return listMovie.size
}
class MovieViewHolder(val binding: MovieItemBinding):RecyclerView.ViewHolder(binding.root){
fun bind(item : MovieItem){
binding.model = item
}
}
}
// activity
class MainActivity : AppCompatActivity() {
lateinit var movieadapter: MovieItemAdapter
lateinit var api: Api
lateinit var repository: MovieRepository
lateinit var movieViewModel: MovieViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.layoutManager = LinearLayoutManager(this)
movieadapter = MovieItemAdapter()
recyclerView.adapter = movieadapter
api = Api.getInstance()
repository = MovieRepository(api)
movieViewModel = ViewModelProvider(this, MovieFactory(repository))
.get(MovieViewModel::class.java)
movieViewModel.makeApiCall()
movieViewModel.listMovie.observe(this, Observer {
movieadapter.setMovieItem(it)
})
}
}
// adapter layout
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="model"
type="com.example.databindingpractice.MovieItem" />
</data>
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="5dp"
android:padding="3dp"
android:orientation="horizontal">
<ImageView
android:layout_width="70dp"
android:layout_height="70dp"
android:id="#+id/img_view" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="#color/black"
android:textSize="18sp"
android:text="#{model.name}"
android:layout_marginLeft="5dp"
android:id="#+id/tv_view"
android:layout_gravity="center" />
</LinearLayout>
</androidx.cardview.widget.CardView>
<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="activity"
type="com.example.databindingpractice.MainActivity" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view" />
</LinearLayout>
In onCreate method of Activity, replace
setContentView(R.layout.activity_main)
With
val binding : MainActivityBinding =
DataBindingUtil.setContentView(this, R.layout.activity_main)
Then you can use
binding.recyclerView.layoutManager = LinearLayoutManager(this)
movieadapter = MovieItemAdapter()
binding.recyclerView.adapter = movieadapter
Also remove this from activity_main.xml
<data>
<variable
name="activity"
type="com.example.databindingpractice.MainActivity" />
</data>
Use this for reference
Update:
Binding adapter can be defined as follows:
#BindingAdapter("moviesList")
fun bindRecyclerView(recyclerView: RecyclerView, list: List<MovieItem>?){
val adapter: MoviesAdapter = recyclerView.adapter as MoviesAdapter
adapter.submitList(list)
}
In activity_main.xml
<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.databindingpractice.MovieViewModel" />
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<androidx.recyclerview.widget.RecyclerView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="#+id/recycler_view"
app:movieList="#{viewModel.listMovie}"/>
</LinearLayout>
I'm trying to call a function from my Data Binding layout, but I'm always receiving some error. I'm trying to set the text on my textView using MyUtilClass's function which I have created. here's my code:
activity_main.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>
<import type="com.example.testapp.User"/>
<import type="com.example.testapp.MyUtilClass"/>
<variable
name="user"
type="User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{MyUtilClass.Companion.changeText(user.firstName)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
MyUtilClass
class MyUtilClass {
companion object {
#JvmStatic
fun changeText(text: String): String {
return text
}
}
}
User
data class User(
val firstName: String,
val lastName: String,
val age: Int,
val loggedIn: Boolean
)
MainActivity.java
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val myUser = User("John", "Doe", 25, true)
binding.user = myUser
}
}
Error:
C:\Users\Stefan\AndroidStudioProjects\TestApp\app\build\generated\source\kapt\debug\com\example\testapp\DataBinderMapperImpl.java:9:
error: cannot find symbol import
com.example.testapp.databinding.ActivityMainBindingImpl;
^ symbol: class ActivityMainBindingImpl location: package
com.example.testapp.databinding
cannot find method changeText(java.lang.String) in class
com.example.testapp.MyUtilClass.Companion Open File
Adding JvmStatic to the changeText() method in MyUtilClass automatically makes it static.
Therefore, you can access it like this in your layout file:
<?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>
<import type="com.example.testapp.User"/>
<import type="com.example.testapp.MyUtilClass"/>
<variable
name="user"
type="User" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:id="#+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{MyUtilClass.changeText(user.firstName)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
You can check this link to find out more:
Kotlin DataBinding pass static function into layout xml
This is the change I made, and it worked. I basically removed class keyword and added object instead.
object MyUtilClass {
#JvmStatic
fun changeText(text: String): String {
return text
}
}
I'm dev-ing using SwipeRefreshLayout in mvvm. so I wrote the codes at viewmodel and xml. when I run this, the progressbar is running but nothing happens. the log doesn't show anything. please let me know what should I fix it. I used this link "AndroidX SwipeRefreshLayout with DataBinding"
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="viewmodel"
type="com.haii.schedulemanager.schedule.ScheduleViewModel" />
</data>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="#+id/swipe_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:refreshing="#{viewmodel.isLoading}"
app:onRefreshListener="#{viewmodel::onRefresh}">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/scheduleListView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</FrameLayout>
</layout>
viewmodel
class ScheduleViewModel #Inject constructor(
private val scheduleRepository: ScheduleRepository
) : ViewModel() {
private val _item = MutableLiveData<List<ScheduleItem>>()
val item: LiveData<List<ScheduleItem>> = _item
private val _oneSchedule = MutableLiveData<ScheduleItem>()
val oneSchedule: LiveData<ScheduleItem> = _oneSchedule
lateinit var mOneSchedule : ScheduleItem
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
fun getWeek(groupName : String){
//_isLoading.value = false
viewModelScope.launch {
val itemList = scheduleRepository.getWeek(groupName)
if(itemList.isEmpty()){
Log.d("TAG","Null")
_item.value=null
}else{
_item.value = itemList
}
//_isLoading.value = true
}
}
fun onRefresh(){
Log.d("TAG","onRefresh")
getWeek("Haii")
}
}
don't forget to clear list when you refresh the page.
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()
}
}
I have checked many answers to find my issue however I was not successful. I have an activity that holds a compound drawable.
<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">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<com.my.profile.widgets.ProfileWidget
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
....
</LinearLayout>
</layout>
This is my ProfileWidget:
class ProfileWidget #JvmOverloads constructor(context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
#Inject lateinit var viewModel: ProfileWidgetViewData
#Inject lateinit var viewActions: ProfileWidgetActions
private val binding: WidgetProfileBinding = DataBindingUtil.inflate(
LayoutInflater.from(context), R.layout.widget_profile, this, true)
// private val binding = WidgetProfileBinding.inflate(LayoutInflater.from(context), this, true)
override fun onAttachedToWindow() {
super.onAttachedToWindow()
setupDependencyInjection()
setupDataBinding()
viewActions.testUI()
}
private fun setupDependencyInjection() {
(context as ProfileActivity).getProfileComponent()?.inject(this)
}
private fun setupDataBinding() {
binding.viewModel = viewModel
}
}
This is its layout:
<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>
<import type="android.view.View" />
<variable
name="viewModel"
type="com.my.profile.widgets.ProfileWidgetViewData" />
</data>
<LinearLayout
android:id="#+id/profilesContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:background="#FF0000"
>
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="profile 1"
android:visibility="#{viewModel.textView_1.get() ? View.VISIBLE : View.INVISIBLE}"/>
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="profile 2"
android:visibility="#{viewModel.textView_2.get() ? View.VISIBLE : View.INVISIBLE}"/>
</LinearLayout>
</layout>
Finally my ViewModel class supposed to make TextViews
visible/invisible.
interface ProfileWidgetViewData {
val textView_1: ObservableBoolean
val textView_2: ObservableBoolean
}
interface ProfileWidgetActions {
fun testUI()
}
class ProfileWidgetViewModelImpl : ProfileWidgetViewData, ProfileWidgetActions {
override val textView_1 = ObservableBoolean(false)
override val textView_2 = ObservableBoolean(false)
override fun testUI() {
setProfilesContainerVisibility(true)
setAddProfileContainerVisibility(true)
}
private fun setProfilesContainerVisibility(isVisible: Boolean) {
textView_1.set(isVisible)
}
private fun setAddProfileContainerVisibility(isVisible: Boolean) {
textView_2.set(isVisible)
}
}
Unfortunately I don't see anything wrong in above codes. When I launch
the app, those two TextView are Invisible although I have set them to be visible.
Check below is added or not in build.gradle(obviously you already added)
apply plugin: 'kotlin-kapt'
android {
dataBinding {
enabled = true
}
}
dependencies {
kapt "com.android.databinding:compiler:3.1.3"
}
And add below line in your xml file for visibility or invisible
<TextView
android:layout_width="match_parent"
android:layout_height="48dp"
android:text="profile 1"
android:visibility="#{safeUnbox(viewModel.textView_1) ? View.VISIBLE : View.INVISIBLE}"/>