There's a fragment on the app I'm working on that the user needs to fill up to 6 EditText. Each pair of EditText represents the type and description of the user's address (as in "Apartment" as the Type and "1201" as the description). According to Brazil's postal service company, it is possible to add up to 3 complements.
At default, I'm showing one pair of EditText that represents the first complement. There's a button that let the user add two more address complements.
I want to use LiveData and DataBinding to update an array of Complements on my ViewModel but it isn't working and I have no idea why.
Is it possible to work with arrays and MutableLiveData?
XML
<android.support.design.widget.TextInputLayout
android:id="#+id/tipo1"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/textView">
<android.support.design.widget.TextInputEditText
android:id="#+id/tipo1edit"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Tipo 1"
android:text="#={viewModel.complementoList.getValues().get(0).descricao}" />
</android.support.design.widget.TextInputLayout>
ViewModel
private MutableLiveData<ArrayList<Complemento>> complementoList;
public MutableLiveData<ArrayList<Complemento>> getComplementoList() {
if (complementoList == null) {
complementoList = new MutableLiveData<>();
ArrayList<Complemento> arrayComplemento = new ArrayList<>();
arrayComplemento.add(new Complemento());
arrayComplemento.add(new Complemento());
arrayComplemento.add(new Complemento());
complementoList.setValue(arrayComplemento);
}
return complementoList;
}
Fragment
final Observer<ArrayList<Complemento>> complementoList = new Observer<ArrayList<Complemento>>() {
#Override
public void onChanged(#Nullable ArrayList<Complemento> complementos) {
//update stuff here
}
};
mViewModel.getComplementoList().observe(this, complementoList);
Related
Good morning, community, I have the following case, I have 1 list of questions with their respective YES/NO answers, which are the checkboxes, what is complicating me is how I can apply 1 validation that only allows marking 1 answer (yes or no), in turn save that answer with its respective position and then save it in a DB.
this is my adapter(preusoadapter.kt)
class preusoadapter(
private val context : Context,
private val listpreguntaspreuso: ArrayList<epreguntas>
) : RecyclerView.Adapter<preusoadapter.PreUsoViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PreUsoViewHolder {
val layoutInflater = LayoutInflater.from(context)
return PreUsoViewHolder(layoutInflater.inflate(R.layout.activity_estructura_listapreuso, parent, false)
)
}
override fun onBindViewHolder(holder: PreUsoViewHolder, position: Int) {
val item = listpreguntaspreuso[position]
holder.render(item)
holder.displayChecked(item.answer)
if (position == 2) {
holder.displayAnswers(setOf(Answer.IVSS, Answer.DSS))
} else if (position == 6) {
holder.displayAnswers(setOf(Answer.FRESERV, Answer.FREDMANO))
} else if (position == 14) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
} else if (position == 18) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
} else if (position == 22) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
} else if (position == 25) {
holder.displayAnswers(setOf(Answer.NA, Answer.SI, Answer.NO))
}
}
override fun getItemCount(): Int = listpreguntaspreuso.size
//CLASE INTERNA PREUSOVIEWHOLDER//
inner class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val binding = ActivityEstructuraListapreusoBinding.bind(view)
private val idpregunta = view.findViewById<TextView>(R.id.txtidpregunta)
private val numeropregunta = view.findViewById<TextView>(R.id.txtnumeropregunta)
private val pregunta = view.findViewById<TextView>(R.id.txtpreguntas)
private val imgestado = view.findViewById<ImageView>(R.id.icosemaforo)
fun render (epreguntas: epreguntas){
idpregunta.text = epreguntas.id_pregunta
numeropregunta.text = epreguntas.num_pregunta
pregunta.text = epreguntas.pregunta
Glide.with(imgestado.context).load(epreguntas.icono_estado).into(imgestado)
}
private val checkboxAnswers = mapOf(
binding.chbksi to Answer.SI,
binding.chbkno to Answer.NO,
binding.chbkna to Answer.NA,
binding.chbkIVSS to Answer.IVSS,
binding.chbkDSS to Answer.DSS,
binding.chbkFSERV to Answer.FRESERV,
binding.chbkfmano to Answer.FREDMANO
)
init {
// set the listener on all the checkboxes
checkboxAnswers.keys.forEach { checkbox ->
checkbox.setOnClickListener { handleCheckboxClick(checkbox) }
}
}
// A function that handles all the checkboxes
private fun handleCheckboxClick(checkbox: CheckBox) {
// get the item for the position the VH is displaying
val item = listpreguntaspreuso[adapterPosition]
// update the item's checked state with the Answer associated with this checkbox
// If it's just been -unchecked-, then that means nothing is checked
checkboxAnswers[checkbox]?.let { answer ->
item.answer = if (!checkbox.isChecked) null else answer
// remember to notify the adapter (so it can redisplay and uncheck any other boxes)
notifyItemChanged(adapterPosition)
}
}
fun displayChecked(answer: Answer?) {
// set the checked state for all the boxes, checked if it matches the answer
// and unchecked otherwise.
// Setting every box either way clears any old state from the last displayed item
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.isChecked = answerType == answer
}
}
fun displayAnswers(answers: Collection<Answer>) {
// iterate over each checkbox/answer pair, hiding or displaying as appropriate
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.visibility = if (answerType in answers) View.VISIBLE else View.GONE
}
}
}
}
and this is my class epreguntas.kt
class epreguntas(
var id_pregunta: String,
var num_pregunta: String,
var pregunta : String,
var icono_estado: String,
var checkvalor: Boolean = false,
var answer: Answer? = null
) {
}
this is my structure i use for my recylcview
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:animateLayoutChanges="true"
app:cardCornerRadius="2dp"
app:cardElevation="4dp"
app:cardUseCompatPadding="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:orientation="vertical">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView
android:id="#+id/icosemaforo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:src="#drawable/ic_android" />
</RelativeLayout>
<LinearLayout
android:id="#+id/contenedor_categoria1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="right"
android:orientation="vertical"
>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<TextView
android:id="#+id/txtnumeropregunta"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="5dp"
android:layout_alignParentTop="true"
android:text="N°"
android:textSize="14sp"
android:textStyle="bold" />
<TextView
android:id="#+id/txtpreguntas"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="5dp"
android:layout_marginEnd="20dp"
android:layout_toStartOf="#+id/contenedorcheck"
android:textSize="14sp"
android:layout_toEndOf="#+id/txtnumeropregunta"
android:textStyle="bold"
android:text="Preguntas" />
<LinearLayout
android:id="#+id/contenedorcheck"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"
android:paddingEnd="20dp"
>
<CheckBox
android:id="#+id/chbksi"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:text="#string/check_si" />
<CheckBox
android:id="#+id/chbkIVSS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:visibility="gone"
android:text="#string/check_IVSS" />
<CheckBox
android:id="#+id/chbkFSERV"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
android:visibility="gone"
android:text="#string/check_freserv" />
<CheckBox
android:id="#+id/chbkno"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
android:text="#string/check_no" />
<CheckBox
android:id="#+id/chbkfmano"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginEnd="10dp"
android:text="#string/check_fredmano" />
<CheckBox
android:id="#+id/chbkDSS"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginEnd="10dp"
android:text="#string/check_DSS" />
<CheckBox
android:id="#+id/chbkna"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
android:layout_marginEnd="10dp"
android:text="#string/check_na" />
</LinearLayout>
</RelativeLayout>
</LinearLayout>
</LinearLayout>
</androidx.cardview.widget.CardView>
It complies with selecting 1 box and unchecking the other, but I realize that when I check the box for question 1 (yes) another question overlaps and my question 1 is hidden, and I see that in some questions the YES/NO/ NA up to IVSS and DSS
example
Like Tenfour04 says, RadioButtons in a RadioGroup would handle the "only one thing can be selected" functionality. (You'd need a different listener to handle its button id has been selected callbacks.)
But since you're already storing the checked state and displaying it, you can handle that yourself - you just need a way to ensure when one checkbox is checked, the others are stored as unchecked.
An easy way to do that is to store an Int which represents the checkbox that's selected. Say 0 for the first, 1 for the second, and a negative number for none. Because each number represents one checked item, by changing the number you're "unchecking" the others.
If you want to store that in your data object (like with checkvaloryes) you can just do:
class epreguntas(
var id_pregunta: String,
...
var checkvaloryes: Boolean = false,
var id_answer: Int
)
Then your checkbox click listeners just have to store the appropriate value, and in onBindHolder you enable the selected checkbox and disable all the others.
This is basically what using a RadioGroup involves too - you get a button ID when the selection changes (-1 for no selection), you store that, and when you display it you fetch that stored ID and set it on the RadioGroup.
The automatic deselection of other buttons (in single-selection mode) is nice and convenient, but setting the current button in onBindViewHolder will trigger the OnCheckedChangedListener and you can get stuck in a loop, so you'd need to avoid that. One way is to only update your stored value (and notify the adapter of the change) if the current selection ID is different to the stored one.
But you can also use checkboxes and click listeners like you already are, you just have to handle their state yourself. Here's a bit of a long explanation of one way to do it, but it's partly about solving other problems you're probably going to run into.
I'm gonna complicate things a bit, because I feel like it will help you out once you understand what's happening - you have a few things to wrangle here. I'm just posting this as one way to approach that store and display problem.
First, I think it would be a good idea to define your options somewhere. Since you have a fixed set of checkboxes, there's a fixed set of answers, right? You could define those with an enum:
enum class Answer {
SI, NO, IVSS, FRESERV, FREDMANO, DSS, NA
}
And you could store that in your data:
class epreguntas(
...
var answer: Answer? = null // using null for 'no answer selected'
)
(You can use that enum elsewhere in your app too - it means your answer data is in a useful data structure, and it's not tied to some detail about your list display, like what order the checkboxes happen to be added in the layout. If you need to turn these enum constants into an Int, e.g. for storage, you can use their ordinal property)
Now you can connect those responses to the checkboxes that represent them. We could do that in the ViewHolder class:
class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
...
// create an answer type lookup for each checkbox in the ViewHolder instance
// I'm going to use your binding object since you have it!
val checkboxAnswers = mapOf(
binding.chbksi to SI,
binding.chbkno to NO,
...
)
That checkboxAnswers map acts as two things - it's a lookup that links each CheckBox in the layout to a specific answer type, and the keys act as a collection of all your CheckBox views, so you can easily do things to all of them together.
Now you can create a click listener that checks which View was clicked, get the matching Answer, and set it:
// I've made this an -inner- class, and it need to be nested inside your Adapter class
// This gives the ViewHolder access to stuff inside the adapter, i.e. listpreguntaspreuso
inner class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
init {
...
// set the listener on all the checkboxes
checkboxAnswers.keys.forEach { checkbox ->
checkbox.setOnClickListener { handleCheckboxClick(checkbox) }
}
// A function that handles all the checkboxes
private fun handleCheckboxClick(checkbox: CheckBox) {
// get the item for the position the VH is displaying
val item = listpreguntaspreuso[adapterPosition]
// update the item's checked state with the Answer associated with this checkbox
// If it's just been -unchecked-, then that means nothing is checked
checkboxAnswers[checkbox]?.let { answer ->
item.answer = if (!checkbox.isChecked) null else answer
// remember to notify the adapter (so it can redisplay and uncheck any other boxes)
notifyItemChanged(adapterPosition)
}
}
This relies on you using a click listener, not a checkedChanged listener, because setting checked state in onBindViewHolder (when you're clearing checkboxes) will trigger that checkedChanged listener. A click listener only fires when the user is the one checking or unchecking a box.
So now you have a click listener that sets the appropriate Answer value for an item. To display it, we could put another function in the ViewHolder:
fun displayChecked(answer: Answer?) {
// set the checked state for all the boxes, checked if it matches the answer
// and unchecked otherwise.
// Setting every box either way clears any old state from the last displayed item
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.isChecked = answerType == answer
}
}
And now you can call that from onBindViewHolder:
override fun onBindViewHolder(holder: PreUsoViewHolder, position: Int) {
val item = listpreguntaspreuso[position]
holder.render(item)
holder.displayChecked(item.answer)
The other reason for doing things this way, is I think your code to make stuff visible/invisible is broken:
else if (position == 14){
holder.itemView.chbkna.visibility = View.VISIBLE
}
This kind of thing won't work - all you're doing is saying "for item 14, make this box visible" - it says nothing about which of the other boxes should be visible, and which should be hidden. You'll have stuff randomly shown or hidden depending on which item happened to be displayed in that ViewHolder before. You need to explicitly say what should be displayed, every time onBindViewHolder runs.
You can do that with a similar function to the displayChecked one we just wrote:
inner class PreUsoViewHolder(view: View) : RecyclerView.ViewHolder(view) {
...
// provide a list of the answers that should be shown
fun displayAnswers(answers: Collection<Answer>) {
// iterate over each checkbox/answer pair, hiding or displaying as appropriate
checkboxAnswers.forEach { (checkbox, answerType) ->
checkbox.visibility = if (answerType in answers) VISIBLE else GONE
}
}
Now you can easily update your displayed boxes in onBindViewHolder:
else if (position == 14){
holder.displayAnswers(setOf(NA, SI, NO))
}
And even better, you could create groups of answer types associated with the question in the data. So instead of hardcoding by position, you can just pull the required types out of the item itself
class epreguntas(
...
answerTypes: Set<Answer>
// onBindViewHolder
holder.displayAnswers(item.answerTypes)
You could even create preset lists for different types of question, like val yesNo = setOf(SI, NO) (or an enum) and reuse those when defining your questions - there are probably only a few combos you're using anyway!
I hope that wasn't too complicated, and the organisation ideas (and the benefits they can give you) make sense. A RadioGroup is simpler, but with the other stuff you'll probably have to deal with, I feel like this is a useful general approach
I have chosen the TextInputEditText for my password field, for using the toggle password feature.
Here is my xml code:
<com.google.android.material.textfield.TextInputLayout
android:layout_width="#dimen/login_width"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginTop="#dimen/password_margin_top"
app:hintEnabled="false"
app:passwordToggleDrawable="#drawable/password_toggle_drawable"
app:passwordToggleEnabled="true">
<com.google.android.material.textfield.TextInputEditText
android:id="#+id/my_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="sans-serif"
android:hint="#string/password"
android:inputType="textPassword"
android:nextFocusDown="#+id/my_login_login"
android:padding="#dimen/field_padding" />
</com.google.android.material.textfield.TextInputLayout>
I have to do some other layout changes on the toggling password. Is there any callback available for this in TextInputLayout?
You can call setEndIconOnClickListener on your TextInputLayout:
textInputLayout.setEndIconOnClickListener { v ->
// Layout changes here
}
However, this removes the click listener responsible for toggling the password transformation method. I would suggest just copying the click listener code in PasswordToggleEndIconDelegate and adding your own functionality on top:
textInputLayout.setEndIconOnClickListener {
val editText: EditText? = textInputLayout.editText
// Store the current cursor position
val selection = editText?.selectionEnd ?: 0
// Check for existing password transformation
val hasPasswordTransformation = editText?.transformationMethod is PasswordTransformationMethod;
if (hasPasswordTransformation) {
editText?.transformationMethod = null
} else {
editText?.transformationMethod = PasswordTransformationMethod.getInstance()
}
// Restore the cursor position
editText?.setSelection(selection)
// Add additional functionality here
}
Edit: This method is only available in material library version 1.1.0-alpha04 onwards and, as of writing, 1.1.0 is still in beta.
I'm having trouble making two Edit Text views that update when one is changed. To provide some context, see the following image:
Also the view in action (can't have it embedded apparently.):
https://i.imgur.com/an6Kodx.mp4
Here, we add targets (T1, T2, T3 etc.), then draw an arc and user may set start and finish points of the camera (gray and red icons respectively.) Then, we get the total move value (in degrees). This value will determine amount the motor will rotate (The app is basically a controller for users to have automated photo-shoots).
What I try to achieve is that, when user enters a photo number, right edittext divides total move degrees to that count and show angle per photo and vice-versa.
However, I'm a bit lost among all the online content demonstrating various examples (like password strength etc.)
I've included DataBinding on gradle.
I've created a custom class (RotaryPhotoShoot) to have a model of three main parameters (angle per shoot, number of photos and total move).
I've moved my cosntraint layout to layout root.
I've created data as seen on following code blocks.
RotaryPhotoShoow.java (my model)
package com.example.macrorecapp.models;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
public class RotaryPhotoShoot extends BaseObservable {
private static final String TAG = "Rotary Photo Shoot";
private float anglePerPhotos;
private int numberOfPhotos;
private int totalMoveDegrees;
public RotaryPhotoShoot(float anglePerPhotos,int numberOfPhotos, int totalMoveDegrees) {
this.anglePerPhotos = anglePerPhotos;
this.numberOfPhotos = numberOfPhotos;
this.totalMoveDegrees = totalMoveDegrees;
}
#Bindable
public float getAnglePerPhotos() {
return anglePerPhotos;
}
#Bindable
public int getNumberOfPhotos() {
return numberOfPhotos;
}
#Bindable
public int getTotalMoveDegrees() {
return totalMoveDegrees;
}
#Bindable
public void setAnglePerPhotos(float anglePerPhotos) {
this.anglePerPhotos = anglePerPhotos;
}
#Bindable
public void setNumberOfPhotos(int numberOfPhotos) {
this.numberOfPhotos = numberOfPhotos;
}
#Bindable
public void setTotalMoveDegrees(int totalMoveDegrees) {
this.totalMoveDegrees = totalMoveDegrees;
}
}
activity_rotary_photo_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="photoShoot"
type="com.example.macrorecapp.models.RotaryPhotoShoot" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/appMainBackground"
tools:context=".features.rotary.RotaryPhotoSettings">
...
<com.example.macrorecapp.features.shared.views.RotaryView
android:id="#+id/rotaryPhotoView"
android:layout_width="360dp"
android:layout_height="360dp"
app:isClockwise="true"
app:targetList="#array/targets"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/h_guideline1"
app:layout_constraintBottom_toTopOf="#id/h_guideline2" />
<EditText
android:id="#+id/numberOfPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{``+photoShoot.numberOfPhotos}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/numberOfPhotosSubtext"
app:layout_constraintEnd_toStartOf="#id/v_guideline"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/numberOfPhotosBG"
android:importantForAutofill="no"
android:inputType="number" />
...
<EditText
android:id="#+id/anglePerPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{``+photoShoot.anglePerPhotos+(char) 0x00B0}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/anglePerPhotosSubtext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#id/v_guideline"
app:layout_constraintTop_toTopOf="#+id/anglePerPhotosBG"
android:importantForAutofill="no"
android:inputType="numberDecimal" />
...
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And finally RotaryPhotoSettings.java
package com.example.macrorecapp.features.rotary;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import com.example.macrorecapp.R;
import com.example.macrorecapp.databinding.ActivityRotaryPhotoSettingsBinding;
//import com.example.macrorecapp.features.shared.views.RotaryView;
import com.example.macrorecapp.models.RotaryPhotoShoot;
public class RotaryPhotoSettings extends AppCompatActivity {
private AlphaAnimation buttonClick = new AlphaAnimation(1F, 0.2F);
//RotaryView mPhotoRotaryView;
//private int mTotalMoveInDegrees;
ActivityRotaryPhotoSettingsBinding mBinding;
RotaryPhotoShoot mRotaryPhotoShoot;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_rotary_photo_settings);
mRotaryPhotoShoot = new RotaryPhotoShoot(6.88f, 25, 178);
mBinding.setPhotoShoot(mRotaryPhotoShoot);
//mPhotoRotaryView = findViewById(R.id.rotaryPhotoView);
//mPhotoRotaryView.addTarget(300);
//mTotalMoveInDegrees = mPhotoRotaryView.getTotalMoveInDegrees();
}
public void goBack(View view) {
view.startAnimation(buttonClick);
finish();
}
public void openThreeSixtyPhotoRotary(View view) {
}
}
Currently I have no errors whatsoever and I'm sure I'll be able change views one way when I programmatically set them in activity. What I feel like I should do is, first use #={} syntax in xmls to begin with. Then I may need to have custom adapters or binders. I've also seen that people use ObservableInt etc. which I got a bit lost. I needed to set my getTotalMove function to set static to get it from RotaryView.java but from then on I couldn't progress.
I'd like to have some pointers what to do onward. I think I can easily handle rounding up numbers where I implement the custom binder/adapter. I know for example the angle may be decimal while the photo count needs to be integer. I will be rounding up photo count and change the angle itself to closest possible value once it is done being edited. I will also need to determine whether start and end points will be included in the interval. Like, for 100 degrees, with 20 degrees per shoot, it'd be like this:
0: S__S__S__S__S__S :100 Thus 6 photos etc.
Before I implement any listeners etc., I figured I could ask here first, because obviously point of using the Data Binding library is to get rid of bunch of listeners and so on. I would appreciate some sort of example where two EditText views change eachother.
Once I figure out how to set non-edited EditText, I'll be dealing with extra considerations I mentioned above, but first I need to get done with two way binding part. I suppose this "two way" is between view and view model, not directly between views, obviously. So I don't know if I can have a trick like #={``+photoShoot.totalMove/photoShoot.anglePerPhoto} etc. in xml.
Anyways, the post is much longer than it is supposed to be, my apologies.
This looked pretty straight-forward at first glance, but the more I look into it, the more complicated it gets. Maybe I'm just confusing myself.
I'd like to add some partial-answer to my own question. I tried to adjust info that I had from following link in my own use case:
https://www.bignerdranch.com/blog/two-way-data-binding-on-android-observing-your-view-with-xml/
I managed to change angle box (one on the right) with following changes:
I deleted some unnecessary variables in my custom view you see above and added a public "Total Move" getter. I use this in my model class RotaryPhotoShoot.
I also added #={} in my xml as you can see updated code below. This combined with notifyPropertyChanged(com.example.macrorecapp.BR.numberOfPhotos); made it possible to update angle box.
Before adding another wall of text, I'll just add the relevant parts of my code for further reference to other people.
RotaryPhotoSettings.java (The activity class that utilizes binding.)
package com.example.macrorecapp.features.rotary;
import androidx.appcompat.app.AppCompatActivity;
import androidx.databinding.DataBindingUtil;
import android.os.Bundle;
import android.view.View;
import android.view.animation.AlphaAnimation;
import com.example.macrorecapp.R;
import com.example.macrorecapp.databinding.ActivityRotaryPhotoSettingsBinding;
import com.example.macrorecapp.models.RotaryPhotoShoot;
public class RotaryPhotoSettings extends AppCompatActivity {
private AlphaAnimation buttonClick = new AlphaAnimation(1F, 0.2F);
//RotaryView mPhotoRotaryView;
//private int mTotalMoveInDegrees;
ActivityRotaryPhotoSettingsBinding mBinding;
RotaryPhotoShoot mRotaryPhotoShoot;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mBinding = DataBindingUtil.setContentView(this, R.layout.activity_rotary_photo_settings);
mRotaryPhotoShoot = new RotaryPhotoShoot(6.88f, 25);
mBinding.setPhotoShoot(mRotaryPhotoShoot);
//mPhotoRotaryView = findViewById(R.id.rotaryPhotoView);
//mPhotoRotaryView.addTarget(300);
//mTotalMoveInDegrees = mPhotoRotaryView.getTotalMoveInDegrees();
}
public void goBack(View view) {
view.startAnimation(buttonClick);
finish();
}
public void openThreeSixtyPhotoRotary(View view) {
}
}
My model class, RotaryPhotoShoot.java
package com.example.macrorecapp.models;
import androidx.databinding.BaseObservable;
import androidx.databinding.Bindable;
import com.example.macrorecapp.features.shared.views.RotaryView;
public class RotaryPhotoShoot extends BaseObservable {
private static final String TAG = "Rotary Photo Shoot";
private float anglePerPhotos;
private int numberOfPhotos;
public RotaryPhotoShoot(float anglePerPhotos, int numberOfPhotos) {
this.anglePerPhotos = anglePerPhotos;
this.numberOfPhotos = numberOfPhotos;
}
#Bindable
public float getAnglePerPhotos() {
return RotaryView.getTotalMoveInDegrees()/(float) numberOfPhotos;
}
#Bindable
public int getNumberOfPhotos() {
return numberOfPhotos;
}
#Bindable
public void setAnglePerPhotos(float anglePerPhotos) {
this.anglePerPhotos = RotaryView.getTotalMoveInDegrees()/numberOfPhotos;
}
#Bindable
public void setNumberOfPhotos(int numberOfPhotos) {
this.numberOfPhotos = numberOfPhotos;
notifyPropertyChanged(com.example.macrorecapp.BR.numberOfPhotos);
notifyPropertyChanged(com.example.macrorecapp.BR.anglePerPhotos);
}
}
The activity layout file that have views in it, activity_rotary_photo_settings.xml
<?xml version="1.0" encoding="utf-8"?>
<layout>
<data>
<variable
name="photoShoot"
type="com.example.macrorecapp.models.RotaryPhotoShoot" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="#color/appMainBackground"
tools:context=".features.rotary.RotaryPhotoSettings"
android:focusable="true"
android:focusableInTouchMode="true">
<com.example.macrorecapp.features.shared.views.RotaryView
android:id="#+id/rotaryPhotoView"
android:layout_width="360dp"
android:layout_height="360dp"
app:isClockwise="true"
app:targetList="#array/targets"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="#id/h_guideline1"
app:layout_constraintBottom_toTopOf="#id/h_guideline2" />
<EditText
android:id="#+id/numberOfPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#={``+photoShoot.numberOfPhotos}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/numberOfPhotosSubtext"
app:layout_constraintEnd_toStartOf="#id/v_guideline"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="#+id/numberOfPhotosBG"
android:importantForAutofill="no"
android:inputType="number" />
<EditText
android:id="#+id/anglePerPhotosEdittext"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="#{``+String.format(`%.2f`, photoShoot.anglePerPhotos)+(char) 0x00B0}"
android:textAlignment="center"
android:ems="4"
android:textSize="15sp"
android:textColor="#FFFFFFFF"
app:layout_constraintBottom_toTopOf="#+id/anglePerPhotosSubtext"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toEndOf="#id/v_guideline"
app:layout_constraintTop_toTopOf="#+id/anglePerPhotosBG"
android:importantForAutofill="no"
android:inputType="numberDecimal" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Current problems that I could use some markers:
I need cross changes, currently I don't know how to tell whether a change is coming from the EditText being changed by typing or not. When the change is coming from other box, I will format/round the value properly and update the EditText view.
I could just use a bool value that I would toggle depending on whether the change is coming from manual editing or value changing progromatically. This would help me prevent infinite loop. However, as I said above, I am not sure what to listen to in order to achieve that.
Another behavior I would like to have is that, when camera start-finish icons are moved and TotalMove (in degrees) changed, I want to have numberOfPhotos fixed and update anglePerPhotos only. I may need to add binding in RotaryView.java for that. If this is an overkill, I may just add a trigger/listener on RotaryPhotoShoot. Currently. when I make a change in numberOfPhotos after I change the camera positions, angle is calculated properly as expected.
One little bug(?) I have is that, I cannot delete the last digit in numberOfPhotos field. See the following webm video below:
https://gfycat.com/distortedyoungdairycow
One thing I've realized is that, getter and setters in model class alone achieves what I need to do. This indeed removes the need to mess around with listeners and custom adapters. Since I'm using two EditTexts interchangeably, I may end up using them still.
Note that you can use any built-in Java functions (see string formatting I used in anglePerPhotos field). If necessary, I know how to import a class in <data></data> block.
I'll add one more link before I finish this update-answer for those who may be lost how to set if Data Binding in their project for the first time:
https://www.youtube.com/watch?v=v4XO_y3RErI
This solved my problem:
android:text="#{String.valueOf(product.quantityInventory + product.quantityShop)}"
I'm trying to make a chat app out of a FireBase sample. I want to make it that when the same user sends a message twice there is no header on the second message. I made it so the header's size is 0dp if current user is the same as the last user, but when I scroll or even hide the keyboard the listview updates and messes it up (all headers dissappear). Can I make it check the element above or is there another way?
public class ChatListAdapter extends FirebaseListAdapter<Chat> {
// The mUsername for this client. We use this to indicate which messages originated from this user
private String mUsername;
private Typeface Consolas;
private String lastUsername = "";
public ChatListAdapter(Query ref, Activity activity, int layout, String mUsername) {
super(ref, Chat.class, layout, activity);
this.mUsername = mUsername;
this.Consolas = Typeface.createFromAsset(activity.getAssets(), "fonts/Consolas.ttf");
}
/**
* Bind an instance of the <code>Chat</code> class to our view. This method is called by <code>FirebaseListAdapter</code>
* when there is a data change, and we are given an instance of a View that corresponds to the layout that we passed
* to the constructor, as well as a single <code>Chat</code> instance that represents the current data to bind.
*
* #param view A view instance corresponding to the layout we passed to the constructor.
* #param chat An instance representing the current state of a chat message
*/
#Override
protected void populateView(View view, Chat chat) {
// Map a Chat object to an entry in our listview
String author = chat.getAuthor();
if (!author.equals(lastUsername)) {
TextView authorText = (TextView) view.findViewById(R.id.author);
authorText.setText(author);
// If the message was sent by this user, color it differently
if (author != null && author.equals(mUsername)) {
authorText.setTextColor(Color.RED);
} else {
authorText.setTextColor(Color.parseColor("#e4de33"));
}
authorText.setTypeface(Consolas);
TextView message = (TextView) view.findViewById(R.id.message);
message.setText(chat.getMessage());
message.setTextColor(Color.WHITE);
message.setTypeface(Consolas);
TextView date = (TextView) view.findViewById(R.id.date);
date.setText(chat.getDate());
date.setTextColor(Color.WHITE);
date.setTypeface(Consolas);
lastUsername = mUsername;
} else {
view.findViewById(R.id.divider).setLayoutParams(new LinearLayout.LayoutParams(0, 0));
view.findViewById(R.id.header).setLayoutParams(new LinearLayout.LayoutParams(0, 0));
TextView message = (TextView) view.findViewById(R.id.message);
message.setText(chat.getMessage());
message.setTextColor(Color.WHITE);
message.setTypeface(Consolas);
}
}
Layout:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout
android:id="#+id/divider"
android:layout_height="1dp"
android:layout_width="fill_parent"
android:background="#color/background_darkblue"
android:orientation="horizontal" />
<LinearLayout
android:id="#+id/header"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="horizontal">
<ImageView
android:layout_width="#dimen/avatar_size"
android:layout_height="#dimen/avatar_size"
android:padding="#dimen/avatar_padding"
android:src="#drawable/g"/>
<TextView
android:id="#+id/author"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"/>
<TextView
android:id="#+id/date"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:layout_gravity="center_vertical"/>
</LinearLayout>
<TextView
android:id="#+id/message"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="#dimen/avatar_padding"
android:layout_marginRight="#dimen/avatar_padding"
android:layout_marginBottom="#dimen/avatar_padding" />
</LinearLayout>
Your code assumes that populateView() is called for each chat message in order. That is not necessarily the case, so you should come up with another way to find if the previous message is from the same user.
The FirebaseListAdapter.populateView() method now takes an extra parameter int position. With this parameter you an explicitly check whether the message before the current one is by the same user:
protected void populateView(View view, Chat chat, int position) {
String author = chat.getAuthor();
String previousAuthor = (position > 0) ? getItem(position-1).getAuthor();
if (!author.equals(previousAuthor)) {
...
So I fixed it by doing this:
String author = chat.getAuthor();
String previousAuthor = "";
int currentIndex = getmModels().indexOf(chat);
if (currentIndex > 0) {
Chat previousObject = (Chat) getItem(currentIndex - 1);
previousAuthor = previousObject.getAuthor();
}
And in the parent class I just had to create a getter for mModels. There was also another bug in this code with the layout changes because the adapter reused my changed params. The solution is to change params in both circumstances (same author and different author).
I am completely new to android development, my problem is that i had two editText boxes in my layout and self created number button 0-9,enterButton and Clr button. now my problem is to get two inputs from user via these number button in two diffrent editText boxes. Help me out!!!
Here is the code`#Override
public void onClick(View view) {
// button clicked
if (view.getId() == R.id.buttonEnter) {
// enter button
}
} else if (view.getId() == R.id.buttonClr) {
// clear button
} else {
// number button
response.setVisibility(View.INVISIBLE);
// here i want to take two inputs by clicking two buttons and display them
int entered1 = Integer.parseInt(view.getTag().toString());
editTxt1.setText(String.valueOf(entered1));
int entered2 = Integer.parseInt(view.getTag().toString());
editTxt2.setText(String.valueOf(entered2));
}
}`
<Button
android:id="#+id/button1"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_margin="1dp"
android:layout_weight="1"
android:gravity="center"
android:padding="5dp"
android:tag="1"
android:text="1"
android:textSize="30sp"
android:textStyle="bold" />`
Ok i get what you are trying to do, but why are you retrieving theTag, which appears to be a String from the view, then parse it to int, just to make it a string again?
besides that, id like to see the Button initialization, so i can understand what exactly the tag is.