In my app I'm using custom view containing a TextInputLayout with the following code:
<?xml version="1.0" encoding="utf-8"?>
<androidx.appcompat.widget.LinearLayoutCompat 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">
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/input_layout"
style="#style/UbiInput2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="-"
android:orientation="horizontal"
app:boxBackgroundColor="#color/white">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/input_value"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:inputType="textPersonName" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.appcompat.widget.LinearLayoutCompat>
And the kotlin
class TextInputView(context: Context, attrs: AttributeSet?) : LinearLayout(context, attrs) {
var errorMessage: String? = ""
init {
inflate(context, R.layout.text_input_view, this)
val attributes = context.obtainStyledAttributes(attrs, R.styleable.TextInputView)
val inputLayout = findViewById<TextInputLayout>(R.id.input_layout)
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputLayout.hint = attributes.getString(R.styleable.TextInputView_hint)
inputValue.hint = attributes.getString(R.styleable.TextInputView_placeholderText)
inputLayout.isExpandedHintEnabled =
attributes.getBoolean(R.styleable.TextInputView_expandedHintEnabled, true)
errorMessage = attributes.getString(R.styleable.TextInputView_errorMessage)
inputLayout.isHelperTextEnabled = false
inputValue.inputType =
attributes.getInt(
R.styleable.TextInputView_android_inputType,
InputType.TYPE_CLASS_TEXT
)
if (attributes.hasValue(R.styleable.TextInputView_android_maxLength)) {
inputValue.filters += InputFilter.LengthFilter(
attributes.getInt(
R.styleable.TextInputView_android_maxLength,
100
)
)
}
inputValue.gravity =
attributes.getInt(R.styleable.TextInputView_android_gravity, Gravity.START)
if (attributes.getBoolean(R.styleable.TextInputView_android_gravity, false)) {
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
if (attributes.getBoolean(R.styleable.TextInputView_helperTextEnabled, false)) {
inputLayout.isHelperTextEnabled = true
inputLayout.helperText = attributes.getString(R.styleable.TextInputView_helperText)
}
inputLayout.startIconDrawable =
attributes.getDrawable(R.styleable.TextInputView_startIconDrawable)
attributes.recycle()
}
}
#BindingAdapter("textValue")
fun TextInputView.setTextValue(value: String?) {
value?.let {
setValue(value)
}
}
#InverseBindingAdapter(attribute = "textValue")
fun TextInputView.getTextValue(): String {
return value()
}
#BindingAdapter("textValueAttrChanged")
fun TextInputView.setListener(textAttrChanged: InverseBindingListener) {
val inputValue = findViewById<TextInputEditText>(R.id.input_value)
inputValue.addTextChangedListener(object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable?) {
textAttrChanged.onChange()
}
})
}
I have the following screen which go to the next fragment on button click. Using Navigation component.
But from the next screen, when I'm pressing back button to come back to the search form, the TextInputLayout values for hint, and helperText are all the same.
The only place I set those is inside the custom view. And from debugging I can see that all the correct values are set at that time.
I'm a bit out of ideas about what's going on there. Any hints would be appreciated.
Material library version used: 1.3.0-alpha04
All codebase has been migrated to view binding according the latest changes
The View is used as followed:
<?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.ubigo.features.v2.products.taxi.SearchViewModel" />
<variable
name="dateFormat"
type="com.ubigo.ubicore.date.UbiDate" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/rental_search_root"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="#color/background">
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_form"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView10">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout3"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/pickup"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:hint="#string/product_start"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.pickupDateTime)}" />
<View
android:id="#+id/divider6"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:background="?android:attr/listDivider"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/pickup" />
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/dropoff"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:hint="#string/product_end"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/divider6"
app:startIconDrawable="#drawable/ic_calendar"
app:textValue="#{dateFormat.getPrettyDate(viewModel.destinationDatetime)}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<com.ubigo.ubicore.ui.LoaderButton
android:id="#+id/rental_search_button"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginTop="32dp"
android:layout_marginEnd="16dp"
android:text="#string/product_rental_show_car"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_location" />
<androidx.cardview.widget.CardView
android:id="#+id/rental_search_location"
style="#style/WhiteCardView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="32dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/rental_search_form">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/constraintLayout4"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.ubigo.ubicore.ui.TextInputView
android:id="#+id/user_location"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:helperText="#string/product_rental_location"
app:helperTextEnabled="true"
app:hint="#string/product_pickup_location"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:startIconDrawable="#drawable/ic_location_on_black_24dp"
app:textValue="#{viewModel.depAddress}" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
<TextView
android:id="#+id/textView10"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="32dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="32dp"
android:text="#string/product_rental_weekend"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And the initialization of the Fragment:
class RentalSearchFragment : Fragment() {
val viewModel: SearchViewModel by viewModel()
private val binding get() = _binding!!
private var _binding: FragmentRentalSearchBinding? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
if (_binding == null) {
_binding = FragmentRentalSearchBinding.inflate(inflater, container, false)
binding.lifecycleOwner = viewLifecycleOwner
binding.viewModel = viewModel
binding.dateFormat = UbiDate()
}
return binding.root
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
I was able to solve this exact problem by implementing onSaveInstanceState, onRestoreInstanceState, dispatchSaveInstanceState, dispatchRestoreInstanceState, and setValues as described here
http://www.devexchanges.info/2016/03/custom-compound-view-in-android.html
The linked above goes into all the necessary detail about how to implement these functions. There's also undoubtedly countless other sources you can find about how to implement this android paradigm.
Related
I have an activity and its layout, I am accessing xml field using ActivityQuotesBinding, activity_quotes.xml has no databinding information.
How did the ActivityQuotesBinding got created.
can some one share some light on this please
I want to do something similar for a fragment but no idea how binding works in this bellow code
thanks in advance
R
<?xml version="1.0" encoding="utf-8"?>
<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"
tools:context=".ui.quotes.QuotesActivity">
<TextView
android:id="#+id/textView_quotes"
android:layout_width="match_parent"
android:layout_height="0dp"
app:layout_constraintHeight_percent="0.55"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:scrollbars="vertical"
android:textAppearance="#style/TextAppearance.AppCompat.Large"
tools:text="I like pineapples. - Thomas Jefferson"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0" />
<EditText
android:id="#+id/editText_quote"
android:layout_width="0dp"
app:layout_constraintWidth_percent="0.7"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:hint="Quote"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView_quotes"
app:layout_constraintVertical_bias="0.0" />
<EditText
android:id="#+id/editText_author"
android:layout_width="0dp"
app:layout_constraintWidth_percent="0.7"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:hint="Author"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/editText_quote"
app:layout_constraintVertical_bias="0.0" />
<Button
android:id="#+id/button_add_quote"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:backgroundTint="?colorAccent"
android:text="Add Quote"
android:textColor="#android:color/white"
app:layout_constraintBottom_toBottomOf="#+id/editText_author"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintStart_toEndOf="#+id/editText_quote"
app:layout_constraintTop_toTopOf="#+id/editText_quote"
app:layout_constraintVertical_bias="0.0"
app:layout_constraintWidth_percent="0.25" />
</androidx.constraintlayout.widget.ConstraintLayout>
Activity
class QuotesActivity : AppCompatActivity() {
private lateinit var binding: ActivityQuotesBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityQuotesBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
initializeUI()
}
private fun initializeUI() {
val factory = InjectorUtils.provideQuotesViewModelFactory()
val viewModel = ViewModelProviders.of(this, factory)
.get(QuotesViewModel::class.java)
viewModel.getQuotes().observe(this, Observer {quotes ->
val stringBuilder = StringBuilder()
quotes.forEach {quote ->
stringBuilder.append("$quote\n\n")
}
binding.textViewQuotes.text = stringBuilder.toString()
})
binding.buttonAddQuote.setOnClickListener {
val quote = Quote(binding.editTextQuote.text.toString(), binding.editTextAuthor.text.toString())
viewModel.addQuotes(quote)
binding.editTextQuote.setText("")
binding.editTextAuthor.setText("")
}
}
}
You are actually using viewbinding, which is totally different from databinding.
When you specify in your module to enable viewbinding, all your layouts will be mapped to a class that follows that name convention that you noticed "LayoutfilenameBinding".
android {
...
buildFeatures {
viewBinding true
}
}
So, to access a fragment layout using viewbinding, you have to do pretty much the same code with some variances.
Lets say that your fragment's layout is fragment_layout.xml
private var _binding: FragmentLayoutBinding? = null
// This property is only valid between onCreateView and
// onDestroyView.
private val binding get() = _binding!!
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
_binding = FragmentLayoutBinding.inflate(inflater, container, false)
val view = binding.root
return view
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
Source:
https://developer.android.com/topic/libraries/view-binding
In my app I load data from API which is using paginating so I have to use paging 3 library
and I use facebook shimmer library to show shimmer effect when items are being loaded
but the problem is always shimmer effect is shown even when items are completely loaded
ArticlePagingAdapter.kt
class ArticlePagingAdapter(private val listener: OnItemClickListener) :
PagingDataAdapter<Article, ArticlePagingAdapter.ViewHolder>(ArticleDiffUtil()) {
private val TAG = ArticlePagingAdapter::class.simpleName
private lateinit var context: Context
var showShimmer: Boolean = true
inner class ViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView), View.OnClickListener {
lateinit var item: Article
val shimmerLayout: ShimmerFrameLayout = itemView.shimmer_layout
val articleImageView: ShapeableImageView = itemView.findViewById(R.id.article_image_view)
val articleTitleTextView: TextView = itemView.findViewById(R.id.article_title_text_view)
val articleEstimatedTextView: TextView =
itemView.findViewById(R.id.article_estimated_read_time_text_view)
val clockImageView: ImageView = itemView.findViewById(R.id.clock_icon)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (showShimmer) {
holder.shimmerLayout.startShimmer()
} else {
holder.shimmerLayout.stopShimmer()
holder.shimmerLayout.setShimmer(null)
holder.articleImageView.background = null
holder.clockImageView.background = null
holder.articleEstimatedTextView.background = null
holder.articleTitleTextView.background = null
val item = getItem(position)
if (item != null) {
Log.d(TAG, "position $position, isNull != null ")
holder.item = item
holder.articleTitleTextView.text = item.title
holder.articleEstimatedTextView.text = (item.estimatedReadingTime.toString()
val requestOptions = RequestOptions().transform(
CenterCrop(), RoundedCorners(
context.resources.getDimension(R.dimen.item_article_image_view_radius)
.toInt()
)
)
Glide.with(context).load(item.imageUrl!!.toUri())
.placeholder(R.drawable.ic_article_placeholder).apply(requestOptions)
.into(holder.articleImageView)
holder.clockImageView.setImageResource(R.drawable.ic_clock)
} else {
Log.d(TAG, "position $position, isNull= null ")
}
}
}
}
ArticleFragmnet.kt
class LatestArticlesFragment : Fragment(), ArticlePagingAdapter.OnItemClickListener {
val TAG = LatestArticlesFragment::class.simpleName
lateinit var binding: FragmentLatestArticlesBinding
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// some unrelated code
val adapter = ArticlePagingAdapter(this)
binding.recyclerView.adapter = adapter
viewModel.getArticles().observe(viewLifecycleOwner) { data ->
lifecycleScope.launch {
adapter.submitData(data)
Log.d(TAG,"data ${data.toString()}")
adapter.showShimmer = false
}
}
}
}
ArticleFragmentViewModel.kt
class LatestArticlesFragmentViewModel #AssistedInject constructor(
private val repository: ArticleRepository, ...) : ViewModel() {
fun getArticles(): LiveData<PagingData<Article>> {
currentArticles = repository.getArticlesStream().cachedIn(viewModelScope)
return currentArticles?.asLiveData()!!
}
}
item_article.xml
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.card.MaterialCardView 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"
style="#style/card_view_style"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/small_margin"
android:clipToPadding="false"
android:paddingBottom="#dimen/_2sdp">
<com.facebook.shimmer.ShimmerFrameLayout
android:id="#+id/shimmer_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="#dimen/very_small_padding">
<com.google.android.material.imageview.ShapeableImageView
android:id="#+id/article_image_view"
android:layout_width="0dp"
android:layout_height="#dimen/_70sdp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="3:2"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#id/article_title_text_view"
app:layout_constraintTop_toTopOf="parent"
tools:src="#drawable/ic_icon" />
<TextView
android:id="#+id/article_title_text_view"
style="#style/Widget.MyWidget.TextView.VerySmall.Bold"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/very_small_margin"
android:layout_marginEnd="#dimen/very_small_margin"
android:ellipsize="end"
android:gravity="start"
android:maxLines="2"
android:minLines="2"
android:textAlignment="textStart"
android:textColor="?attr/textColor"
app:layout_constraintBottom_toTopOf="#id/clock_icon"
app:layout_constraintEnd_toStartOf="#id/article_image_view"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#id/article_image_view"
tools:text="This is a dummy title" />
<TextView
android:id="#+id/article_estimated_read_time_text_view"
style="#style/Widget.MyWidget.TextView.VerySmall"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="#dimen/very_small_margin"
android:background="#color/shimmer_background"
android:textColor="?attr/textColor"
app:layout_constraintBottom_toBottomOf="#id/article_image_view"
app:layout_constraintEnd_toEndOf="#id/article_title_text_view"
app:layout_constraintStart_toEndOf="#id/clock_icon"
app:layout_constraintTop_toBottomOf="#id/article_title_text_view"
tools:text="5 min" />
<ImageView
android:id="#+id/clock_icon"
android:layout_width="#dimen/_10sdp"
android:layout_height="#dimen/_10sdp"
android:background="#color/shimmer_background"
app:layout_constraintBottom_toBottomOf="#id/article_estimated_read_time_text_view"
app:layout_constraintEnd_toStartOf="#id/article_estimated_read_time_text_view"
app:layout_constraintStart_toStartOf="#id/article_title_text_view"
app:layout_constraintTop_toTopOf="#id/article_estimated_read_time_text_view"
tools:src="#drawable/ic_clock" />
</androidx.constraintlayout.widget.ConstraintLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
</com.google.android.material.card.MaterialCardView>
fragment_article_layout.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>
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/root_container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?attr/articleBackground">
<TextView
android:id="#+id/article_text_view"
style="#style/Widget.MyWidget.TextView.Large.Bold"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="#dimen/medium_margin"
android:text="#string/articles"
android:textColor="?attr/textColor"
app:layout_constraintStart_toStartOf="#id/left_guideline"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="#+id/line_separator"
style="#style/Divider"
android:layout_marginStart="#dimen/_10sdp"
app:layout_constraintBottom_toBottomOf="#id/article_text_view"
app:layout_constraintEnd_toEndOf="#id/right_guideline"
app:layout_constraintStart_toEndOf="#id/article_text_view"
app:layout_constraintTop_toTopOf="#id/article_text_view"
app:layout_constraintVertical_chainStyle="packed" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="#android:color/transparent"
android:clipToPadding="false"
android:orientation="vertical"
android:paddingHorizontal="#dimen/small_padding"
android:paddingTop="#dimen/small_padding"
android:paddingBottom="#dimen/small_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintTop_toBottomOf="#id/article_text_view"
tools:itemCount="8"
tools:listitem="#layout/item_article" />
<ImageView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scaleType="fitXY"
android:src="?attr/listFadeBackground"
app:layout_constraintTop_toTopOf="#id/recycler_view" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/left_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.03" />
<androidx.constraintlayout.widget.Guideline
android:id="#+id/right_guideline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
app:layout_constraintGuide_percent="0.97" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
So I'm fairly new to Kotlin.
I am trying to create an onClickListener on an image button to open the share interface, so that the particular video from the recyclerView can be shared via SMS, etc.
I followed various tutorials on how to do this as I am trying to execute this within a fragment, and the app just keeps crashing when I try to open the fragment in question.
I get the following error
java.lang.ClassCastException: java.lang.Integer cannot be cast to android.widget.ImageButton
Here is what my fragment code looks like:
SearchFragment.kt
class SearchFragment : Fragment(), View.OnClickListener
{
private var layoutManager: RecyclerView.LayoutManager? = null
private var adapter: RecyclerView.Adapter<ClipAdapter.ViewHolder>? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View
{
val rootView = inflater.inflate(R.layout.fragment_search, container, false)
loadData()
return rootView
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?)
{
val shareBtn = view.findViewById<ImageButton>(R.id.button_to_share)
shareBtn.setOnClickListener(this)
}
private fun loadData()
{
val service = TwitchServiceBuilder.buildService(TwitchService::class.java)
val requestCall = service.getClips("anerdfails")
requestCall.enqueue(object : Callback<List<Clip>>
{
override fun onResponse(
call: Call<List<Clip>>,
response: Response<List<Clip>>
)
{
if (response.isSuccessful)
{
//process data
recyclerView.layoutManager = GridLayoutManager(activity, 2)
recyclerView.adapter = ClipAdapter(response.body()!!)
} else
{
//output alert
AlertDialog.Builder(activity!!)
.setTitle("API error")
.setMessage("Response, but something went wrong ${response.message()}")
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setIcon(android.R.drawable.ic_dialog_alert)
.show()
}
}
override fun onFailure(call: Call<List<Clip>>, t: Throwable)
{
//process failure
AlertDialog.Builder(activity!!)
.setTitle("API error")
.setMessage("No response, and something went wrong $t")
.setPositiveButton(android.R.string.ok) { _, _ -> }
.setIcon(android.R.drawable.ic_dialog_alert)
.show()
}
})
}
override fun onClick(v: View?)
{
Toast.makeText(activity, "Its toast!", Toast.LENGTH_SHORT).show()
}
}
And here are my 2 layouts for the RecyclerView:
fragment_search.xml
<?xml version="1.0" encoding="utf-8"?>
<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:paddingStart="15dp"
android:paddingTop="?attr/actionBarSize"
android:paddingEnd="15dp"
tools:context=".ui.search.SearchFragment">
<androidx.appcompat.widget.SearchView
android:background="#drawable/search_bar"
android:id="#+id/clipSearch"
android:layout_width="fill_parent"
android:layout_height="50dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="5dp"
android:focusable="true"
android:focusableInTouchMode="true"
app:layout_constraintBottom_toTopOf="#+id/recyclerView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/recyclerView"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="15dp"
android:layout_marginBottom="75dp"
android:scrollbars="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#id/clipSearch" />
</androidx.constraintlayout.widget.ConstraintLayout>
clip_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content"
tools:context=".ui.search.SearchFragment">
<VideoView
android:id="#+id/videoClip"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="8dp"
app:layout_constraintDimensionRatio="w,2:3"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="#+id/txtTitle"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/videoClip"
tools:ignore="HardcodedText" />
<TextView
android:id="#+id/txtChannel"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtTitle"
tools:ignore="HardcodedText" />
<TextView
android:id="#+id/txtGame"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtChannel"
tools:ignore="HardcodedText" />
<TextView
android:id="#+id/txtViews"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
android:text="TextView"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/txtGame"
tools:ignore="HardcodedText" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="30dp"
android:orientation="horizontal"
android:weightSum="2"
android:layout_marginTop="15dp"
app:layout_constraintTop_toBottomOf="#+id/txtViews">
<ImageButton
android:id="#+id/favouriteButton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#null"
android:scaleType="fitCenter"
android:src="#drawable/ic_baseline_favorite_border_24" />
<ImageButton
android:id="#+id/button_to_share"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="#null"
android:scaleType="fitCenter"
android:src="#drawable/ic_baseline_share_24" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
Seems like a simple mistake on my part, but I'm pulling my hair out trying to work out what I've done wrong to cause the error on loading.
Any help would be appreciated.
I see you're trying to find the button "ShareBtn" inside the fragment which is totally wrong.
the "ShareBtn" doesn't belong to the fragment, it belongs to the viewHolder which you have created inside "ClipAdapter"
What you need to do is creating an interface inside "ClipAdapter" and create an object from it inside the Adapter
then call the method which is should the clickListener delegates the click to it
lastly, you should implement it inside the fragment and put whatever logic you want
This link will help you implement it
You are casting ID of view which is an Integer to ImageButton which is a View in this line
val shareBtn = R.id.button_to_share as ImageButton
You should use this instead
val shareBtn = findViewById<ImageButton>(R.id.button_to_share)
UPDATE
Also you should find views after fragment view got created. It means you should call `findViewById` inside `onViewCreated` and not inside `onCreateView`. If you try to find views before view of fragment gets created then you get `NullPointerException` since there is no view yet.
Using LiveData<PagedList> to display a recycler view that hits a Room database. When I do a search on screen, the number of items returned is less, however, there seems to be a lot of extra white space between the recycler view and the other buttons. The recycler view itself shrinks, and then moves down on the screen.
Here is the way it looks before the data is refreshed (before I do a search):
Here is after:
Here is my adapter for the recyclerView:
class WorkPackagesRecyclerAdapter(
private val onWorkPackageClickListener: OnClickWorkPackage
) : PagedListAdapter<Workpackage,
WorkPackagesRecyclerAdapter.WorkPackagesViewHolder>(
REPO_COMPARATOR
) {
interface OnClickWorkPackage {
fun clickWorkPackage(workPackageId: String)
}
override fun onCreateViewHolder(viewGroup: ViewGroup, i: Int): WorkPackagesViewHolder {
val inflater = LayoutInflater.from(viewGroup.context)
val binding = inflate<ItemWorkPackageBinding>(
inflater,
R.layout.item_work_package,
viewGroup,
false
)
return WorkPackagesViewHolder(binding, viewGroup.context)
}
override fun onBindViewHolder(holder: WorkPackagesViewHolder, position: Int)
{
getItem(position)?.let {
holder.bind(it)
}
}
inner class WorkPackagesViewHolder(
internal var binding: ItemWorkPackageBinding,
internal val context: Context
) : RecyclerView.ViewHolder(binding.root), KoinComponent {
fun bind(data: Workpackage) {
val itemWorkPackagesViewModel: ItemWorkPackagesViewModel by inject{ parametersOf(data)}
this.binding.listItem.setOnClickListener {
onWorkPackageClickListener.clickWorkPackage(data.id)
}
this.binding.viewmodel = itemWorkPackagesViewModel
this.binding.executePendingBindings()
}
}
companion object {
private val REPO_COMPARATOR =
object : DiffUtil.ItemCallback<Workpackage>() {
override fun areItemsTheSame(
oldItem: Workpackage,
newItem: Workpackage
): Boolean =
oldItem.id == newItem.id
override fun areContentsTheSame(
oldItem: Workpackage,
newItem: Workpackage
): Boolean =
oldItem == newItem
}
}
Here is how I set up the PagedList:
val searchQuery: MutableLiveData<SearchAndSort> = MutableLiveData(
SearchAndSort("",
WorkpackagesRepository.Companion.SortedBy.WorkPackageNumber,
AscendDescend.ASC))
var workPackagesList = Transformations.switchMap(searchQuery) { searchQuery ->
val factory = workPackageStorageDao.searchWorkpackages(
searchQuery.searchText,
searchQuery.sortBy.type + " " + searchQuery.ascendDescend.text
)
val pagedListBuilder = LivePagedListBuilder<Int, Workpackage>(factory, pagingLimit)
pagedListBuilder.build()
}
Here is where I am observing the adapter:
workPackagesViewModel.workPackagesList.observe(this, Observer { wpList ->
wpList ?: return#Observer
adapter = WorkPackagesRecyclerAdapter(this)
adapter.submitList(wpList)
binding.workPackagesRecyclerView.adapter = adapter
adapter.notifyDataSetChanged()
})
Here is the layout of the recycler view
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:tools="http://schemas.android.com/tools"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="com.bechtel.pf.ui.workpackages.WorkPackagesViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="727dp"
tools:layout_editor_absoluteY="1dp">
<EditText
android:id="#+id/search_bar_edit_text"
android:layout_width="match_parent"
android:layout_height="55dp"
android:layout_marginTop="#dimen/work_package_margin"
android:hint="#string/work_packages_search_hint"
app:layout_constraintBottom_toBottomOf="#id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<ImageButton
android:id="#+id/search_bar_magnifying_glass"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginTop="#dimen/work_package_margin"
android:background="#drawable/icons_search"
app:layout_constraintBottom_toBottomOf="#id/guideline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
/>
<androidx.constraintlayout.widget.Guideline
android:id="#+id/guideline"
android:layout_width="match_parent"
android:layout_height="0dp"
android:orientation="horizontal"
app:layout_constraintGuide_begin="#dimen/work_package_search_guideline_start"
/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btnSortBy"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginTop="5dp"
android:layout_marginStart="10dp"
android:layout_marginEnd="10dp"
android:backgroundTint="#color/colorPrimary"
app:layout_constraintEnd_toStartOf="#+id/btnAscendDescend"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/guideline"
app:layout_constraintHorizontal_chainStyle="spread"
android:text="#string/work_packages_sort"
/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btnAscendDescend"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="10dp"
android:backgroundTint="#color/colorPrimary"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintEnd_toStartOf="#+id/btnFilterBy"
app:layout_constraintStart_toEndOf="#+id/btnSortBy"
app:layout_constraintTop_toBottomOf="#+id/guideline"
app:layout_constraintHorizontal_chainStyle="spread"
android:text="#string/work_packages_ascend"
android:onClick="#{() -> viewModel.ascendingDescending()}"
/>
<com.google.android.material.button.MaterialButton
android:id="#+id/btnFilterBy"
android:layout_width="0dp"
android:layout_height="45dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="10dp"
android:backgroundTint="#color/colorPrimary"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintHorizontal_chainStyle="spread"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="#+id/btnAscendDescend"
app:layout_constraintTop_toBottomOf="#+id/guideline"
android:text="#string/work_packages_filter"/>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/workPackagesRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
android:layout_marginTop="75dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnFilterBy">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
Anyone ever seen anything like this before? I've also made sure that I am using layout_height = wrap_content in my recyclerview layout and item.xml files.
Screenshot inspector:
OK, it looks like I had set the recyclerview layout_constraintBottom_toBottomOf was set to 'parent' and since the total height of the recyclerview shrunk when the number of items shrunk, it was moving the view to the bottom of the screen. I also could get rid of the top margin attribute and the recycler view stayed below the buttons. So now I have:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/workPackagesRecyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/btnFilterBy"/>
And it all looks good!
I have a question about how the visibility of an object in the ViewModel will change.
I'll tell you my case: I have a login interface that has two Edittexts and two buttons, a button and an Edittext are invisible by default, and I want the button that is visible to make the first Edittext and the button that I clicked invisible and make the second button and the second Edittext visible. And here's the problem, I could do all this in Activity, but I need to do it in ViewModel and I have no idea how to access the xml components from there.
I know all this is messy so I'll send the classes and if someone could tell me how to do this I'd appreciate it. Thank you. Thank you.
Login 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.quobis.sippo.ecco.viewmodel.LoginViewModel"/>
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="LoginUser">
<ImageView
android:layout_width="250dp"
android:layout_height="200dp"
android:src="#drawable/logom"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.2"
/>
<EditText
android:id="#+id/usr"
android:layout_width="200dp"
android:layout_height="50dp"
android:hint="#string/hint_user"
android:textSize="18sp"
android:textColorPrimary="#color/colorLetterLogin"
android:backgroundTint="#color/colorBackButtLogin"
android:elevation="20dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
app:addTextChangedListener="#{viewModel.emailTextWatcher}"
/>
<Button
android:id="#+id/btn_usr"
android:layout_width="55dp"
android:layout_height="50dp"
android:background="#color/colorBackButtLogin"
android:drawableBottom="#drawable/ic_keyboard_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="260dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:onClick="#{viewModel::onUserClicked}"
/>
<EditText
android:id="#+id/pass"
android:layout_width="200dp"
android:layout_height="50dp"
android:hint="#string/hint_pass"
android:textSize="18sp"
android:shape="rectangle"
android:inputType="textPassword"
android:textColorPrimary="#color/colorLetterLogin"
android:backgroundTint="#color/colorBackButtLogin"
android:elevation="20dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:visibility="invisible"
android:onClick="#{viewModel::onLoginClicked}"
/>
<Button
android:id="#+id/btn_pass"
android:layout_width="55dp"
android:layout_height="50dp"
android:background="#color/colorBackButtLogin"
android:drawableBottom="#drawable/ic_keyboard_arrow_right"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="260dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.6"
android:visibility="invisible"
/>
<Spinner
android:id="#+id/spinner_usr"
android:layout_width="120dp"
android:layout_height="40dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginLeft="40dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.85"
/>
</android.support.constraint.ConstraintLayout>
</layout>
ViewModel:
class LoginViewModel(private val listener: LoginResultCallbacks) :
ViewModel() {
private val user: UserModel
var userp ="jorge"
init {
this.user= UserModel(email = "")
}
fun emailTextWatcher(): TextWatcher {
return object : TextWatcher {
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {
user.setEmail(s.toString())
}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
}
override fun afterTextChanged(s: Editable) {
}
}
}
fun onUserClicked(v: View) {
}
fun onLoginClicked(v:View) {
if (user.getEmail() == userp)
listener.onSucces("Correcto")
else
listener.onError("Fallo")
}
}
Note: the method to change the visibility would be the onUserClicked.
Main activity:
class LoginUser : AppCompatActivity(), LoginResultCallbacks {
lateinit var binding: ActivityLoginUserBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_login_user)
binding.viewModel = ViewModelProviders.of(this, LoginViewModelFactory(this)).get(LoginViewModel::class.java)
var languages = arrayOf("English", "EspaƱol", "Galego")
val spinner = binding.spinnerUsr
if (spinner != null) {
val arrayAdapter = ArrayAdapter(this, R.layout.spinner_item, languages)
spinner.adapter = arrayAdapter
}
}
override fun onSucces(message: String) {
Toast.makeText(this,"Login bueno", Toast.LENGTH_SHORT).show()
}
#SuppressLint("ResourceAsColor")
override fun onError(message: String) {
binding.btnUsr
btn_usr.setBackgroundColor(R.color.colorFailLogin)
}
}
You can use Android databinding. I will provide a rough example.
You need to create ObservableFields that your views in XML can bind to.
For example
val isVisible : ObservableField<Boolean> = ObservableField();
Then create a binding adapter
#BindingAdapter("customVisibility")
fun setVisibility(view : View, visible : Boolean) {
view.visibility = if (visible) View.VISIBLE else View.INVISIBLE
}
Then bind in your XML
app:customVisibility="#{viewModel.isVisible}"
Then you can change the visibility of the views just by modifying the isVisible property of your view model.
I have wrote a primer in Android Databinding and Functional MVVM. You can check it for further details (written in Kotlin)
https://medium.com/tompee/android-data-binding-and-functional-mvvm-b311e4c98d