View won't update in dialog after creation - android

Surely a simple question to ask but I've tried for hours and I can't seem to get the problem !
I have a DialogFragment which contains a
<com.google.android.material.textfield.TextInputLayout
android:id="#+id/interval_input_layout"
style="#style/Widget.MaterialComponents.TextInputLayout.FilledBox.ExposedDropdownMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:boxStrokeWidth="0dp">
<AutoCompleteTextView
android:id="#+id/time_interval_input"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none"
tools:ignore="LabelFor" />
</com.google.android.material.textfield.TextInputLayout>
This is the listener set on this AutoCompleteView
binding.untilInput.onItemClickListener =
AdapterView.OnItemClickListener { parent, view, position, id ->
when (position) {
0 -> {
binding.numberLayout.visibility = View.GONE
}
1 -> {
binding.numberLayout.visibility = View.GONE
}
2 -> {
binding.numberLayout.visibility = View.VISIBLE
}
}
}
While number layout is just a linear layout like this
<androidx.appcompat.widget.LinearLayoutCompat
android:id="#+id/number_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:orientation="horizontal"
android:padding="16dp">
<com.google.android.material.textview.MaterialTextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:text="For number of events"
android:textColor="?android:textColorSecondary" />
</androidx.appcompat.widget.LinearLayoutCompat>
At first the listener wasn't being called so I set it like this instead of directly calling the function setOnItemClickListener (don't know why that wasn't working) , Now the listener is being called , I even put a breakpoint and debugged it and its setting visibility but its not taking any effect and it does not cause any error so I can't seem to get the problem here

Using a DialogFragment I inflated the view layout with the use of databinding in the method OnCreateView & then in OnViewCreated I worked with my view and caused changes to UI and set listeners to it and again cause changes to UI
in OnCreateDialog I called both of these methods onCreateView (to get the view and set it to dialog) and onViewCreated to setup the view
I tried to find the view using findViewById just to ensure if the databinding was working fine and it was working fine but visibility still won't change and the view won't update !
I still have't gotten to the root of the problem but it seems you have to store the view inside a variable so you don't lose a reference to it when onClickListener is called and in my opinion just when the onClickListener is called getting the view from the binding becomes invalid and it should but the small problem is that I am not calling onDestroy to make the binding null because the binding was invalid at that time !
Now I am just storing a refrence to binding like this
val myvar = binding.monthLayout
and I am capturing the value of myvar inside the onClickListener rather than using the binding

Related

How to make an ImageButton disappear when disabled using only xml?

This is my first android project,
Matter of fact first programming project ever.
I have an ImageButton that I want to hide when it's disabled,
The enabled/disabled state is done using xml connection to a viewmodel,
so I can't reference the fragment to do it programmatically.
I tried using a selectorlist to pass in a grayed out version of the image, but I want to hide the button completely not just the src image.
the button in the fragment layout:
<ImageButton
android:id="#+id/work_button"
android:src="#drawable/ic_desktop_windows_black"
android:layout_width="120dp"
android:layout_height="wrap_content"
android:enabled="#{timerViewModel.pcButtonState}"
android:onClick="#{() -> timerViewModel.activityTypeWork()}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="#+id/gaming_button"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.40"
tools:ignore="ContentDescription" />
The variable controlling the disabled/enabled state:
val pcButtonState = Transformations.map(lastSession){
it == null
}
This is done so the button is enabled only when last session is null
So is there anyway to control android:visibility or android:alpha through a selector, or something?
Thank you...
Edit
Problem solved, both my answer and Henry Twist's are correct,
Personally I prefer Henry's approach because it's way easier to implement and maintain.
I don't think there is a need to make the solution this complex, if a button should be hidden when disabled and visible when enabled, you don't need to use the enabled/disabled state at all, you can just set the visibility dependant on timerViewModel.pcButtonState.
So for example:
android:enabled="true"
android:visibility="#{timerViewModel.pcButtonState ? View.VISIBLE : View.GONE}"
Edit
In order you use external classes in data binding you have to import them in your data tag, so:
<data>
<import type="android.view.View" />
</data>
Just so, if anyone else came across a similar weird situation
I found a workaround,
By setting up an observer object in the fragment to observe the variable controlling the state
val pcButton = binding.pcButton
timerViewModel.pcButtonState.observe(viewLifecycleOwner, Observer { state ->
state.let {
if (it == true) {
binding.pcButton.visibility = View.VISIBLE
}
}
})
Now I can programmatically adjust the button properties according to the state of the variable

Continues updating view with data binding

I am using data binding for setting data to recyclerview item and I am using #BindingAdapter for custom attrinbutes.
Now i want to update the text of a textview in each second. The textview is displaying a text(time ago) with related to a timestamp, i have created a #BindingAdapter function by passing the timestamp, which works perfectly for setting the text single time, but i want to update it in each second.
<TextView
android:id="#+id/tvTime"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textSize="12sp"
android:textStyle="italic"
app:timeago="#{order.DateStamp}"
app:layout_constraintBottom_toTopOf="#id/tvOrderId"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="Just now" />
Following is my binding adapter
#BindingAdapter("timeago")
public static void setTimeAgo(TextView view, long time) {
view.setText(getTimeAgo(time));
}
this is my layout file
You can use EchoBind class, BindingAdapter for this.
The binding should be in two way from XML to model/adapter or model/adapter to XML.
To get detailed documentation refer these link1 & link2.

why the checkBox is check when setChecked(false) is called

having a few dynamically inflated/added checkbox, when some are checked the rotate or minimize the app (for easy to trigger the case, turn on the 'Dont keep activity alive'), the restored UI view shows all checkbox checked.
When os do saveInstance we store the checked items in a list, and when the OS to restore the fragment, we get the list of checked items and when re inflate the checkbox row it calls either setChecked(true) or setChecked(false) based on the list.
But after that all the checkbox shows as checked, although in the debug it clearly shows only the checked ones are used 'true' and others are used 'false' with setChecked().
Anyone experienced the same, or knows why setChecked() on the individual checkBox instance does not do what is called?
itemList = [A, B, C, D, E]
checkedListSet = [A, B]
void insertOneRow(ViewGroup container, Item item) {
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View itemLayout = inflater.inflate(R.layout.item_row, null, false);
TextView txt = (TextView)itemLayout.findViewById(R.id.text);
txt.setText(item.toString());
container.addView(itemLayout, container.getChildCount());
CheckBox checkbox = (CheckBox)itemLayout.findViewById(R.id.checkbox);
if (checkbox != null) {
boolean isChecked = (checkedListSet.get(item) != null);
Log.i(“insertOneRow(), isChecked:"+ isChecked +", item:”+item);
checkbox.setChecked(isChecked); //<== trace shows only A and B are set with true
}
}
item_row.xml
<LinearLayout
android:layout_width="match_parent"
android:layout_height="36dp"
android:orientation="horizontal"
>
<CheckBox
android:id="#+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:checked="false" />
<TextView
android:id="#+id/text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
/>
</LinearLayout>
The reason all your checkboxes are getting checked is that they all have the same id. If either one of them is checked and they are recreated(either because of orientation change or reattaching a fragment), Android system tries to restore their state and it identifies them based on their ids. Hence all of them get checked.
android:saveEnabled flag tells Android system whether to save their state and try to restore later or not.
Since you already have a mechanism in place to restore their state setting android:saveEnabled to false works for you.
See this question where a similar thing happens with EditText : Why does Android change the value of EditTexts with same id?
still not sure why, but after put in android:saveEnabled="false" the same code start to work. does anyone know why it has to have android:saveEnabled="false"?
<CheckBox
android:saveEnabled="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
/>
checkedListSet.get(item) != null
This method will return true if there is an item in your list and it's not null.
You are assigning the result of this expression to your isChecked boolean.
Thus every time this expression returns true, your check box will be setChecked true.
I do not know what exact condition you want to check before setting the check box so i can't help you with that.

editTexts changing their value when rotating the device

The Context
My application architecture is very similar to the default called "Swipe Views" when you create a new android project.
One activity, 3 Fragments, you can swipe through them. It uses the support library.
everything is up to date.
The device I'm testing on is a galaxy nexus running android 4.1.2
Support library r11, and SDK versions min=14 target=16
The Problem
The second Fragment holds a bunch of EditText which contain various numerical values read from sharedPreferences. When the Activity is first created, everything is fine the EditViews all have their correct value, but if I modify one of the values to, for instance 555, and then I rotate the device, all the EditText are now displaying 555 !
There is only one setText in the whole program and it is displayed later in this post, I tried to remove it, as expected when starting the activity all the EditTexts display the default value (666), I modify one and rotate the device, they all display the new value!
The details
One activity with a SectionsPagerAdapter, a ViewPager and several fragments for each page.
In one of the fragments (ConfigurationFragment) in the onViewCreated method I dynamically create lines with and EditText and an ImageButton.
those lines are actually in a layout (xml file) which I inflate for each line to add, then I get the editText and call setText to set it's value to what it should be.
onViewCreated method:
public void onViewCreated(View view, Bundle savedInstanceState)
{
// populate ports list on view creation
ArrayList<String> listenPorts = mServerManagerThread.getListenPorts();
for (String port : listenPorts)
{
EditText v = ((EditText) addListenPortItem().getChildAt(0));
v.setText(port);
}
super.onViewCreated(view, savedInstanceState);
}
naturally the method getListenPorts returns the same list each time it is called (on startup and when the device is rotated)
what addListenPortItem basically does:
// Instantiate a new "row" view.
final ViewGroup newView = (ViewGroup) LayoutInflater.from(viewGroup.getContext()).inflate(itemResource,
viewGroup, false);
// Set a click listener for the "X" button in the row that will remove
// the row.
newView.findViewById(R.id.delete_button).setOnClickListener(
new OnRemoveClickListener(mContainer, viewGroup, newView, emptyListTextResource));
viewGroup.addView(newView);
viewGroup is a linearLayout which will hold the newly created views
itemResource is the following layout
layout of one line:
<?xml version="1.0" encoding="utf-8"?>
<!--
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent" >
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="?android:listPreferredItemHeightSmall"
android:layout_marginTop="8dp"
android:divider="?android:dividerVertical"
android:dividerPadding="8dp"
android:gravity="right"
android:orientation="horizontal"
android:showDividers="middle" >
<EditText
android:id="#+id/editPort"
android:layout_width="100dp"
android:layout_height="wrap_content"
android:ems="10"
android:inputType="number"
android:text="666" >
</EditText>
<ImageButton
android:id="#+id/delete_button"
style="?android:attr/borderlessButtonStyle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="#string/action_remove_item"
android:src="#drawable/content_remove" />
</LinearLayout>
any idea?
I've been staring at this bug for a couple hours now and I really have no clue what's going on ... I'm starting to suspect a bug in the SDK or the support library, but I know from experience the bug is most often in my brain :)
I found the solution: I moved the code from onViewCreated to onViewStateRestored(..) so that filling the EditTexts with their value is done after the state has been restored.
The problem was that all the EditTexts have the same id so when restoring the state it was messing it up.
That's not a bug, so don't be worried. The problem is that if you rotate the screen the Activity and the Fragments will be recreated. onCreateView() will be called again and this mixes things up and therefore you have such a weird behaviour. You can fix this by overriding these thwo methods:
#Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
// Read values from the "savedInstanceState"-object and put them in your textview
}
#Override
protected void onSaveInstanceState(Bundle outState) {
// Save the values you need from your textview into "outState"-object
super.onSaveInstanceState(outState);
}

Possibility to add parameters in button xml?

I currently have an activity with some buttons.
In my xml, buttons are defined like this:
<ImageButton (...) android:onClick="GoToPageX"/>
and I have in my activity:
public void GotoPageX() {
startActivity(new Intent(this, PageX.class));
finish();
}
The problem is that I have hundreds of buttons and do not want to write
<ImageButton (...) android:onClick="GoToPage1"/>
<ImageButton (...) android:onClick="GoToPage2"/>
<ImageButton (...) android:onClick="GoToPage3"/>
...
<ImageButton (...) android:onClick="GoToPage100"/>
and all the scripts.
I am now using
public void GotoPage( int i) {
startActivity(new Intent(getBaseContext(), activities.get(i)));
finish();
}
and would like to give the parameter i from the xml, is that possible?
Thank a lot for any help.
It is not directly possible. However, maybe you could use android:tag to get your parameter.
<ImageButton (...) android:onClick="goToPage" android:tag="25"/>
public void goToPage(View v) {
String pageNumber = v.getTag().toString();
/* ... */
}
You could also do this by enabling data binding and using a lambda expression for the onClick value. This way is especially useful if you plan to use multiple inputs of different types. Here's an example of a simple MainActivity.xml in which this strategy is used.
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="main" type="com.example.android.myapp.MainActivity" />
</data>
<LinearLayout android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton (...) android:onClick='#{() -> main.GotoPage(1,"one")}'/>
<ImageButton (...) android:onClick='#{() -> main.GotoPage(2,"two")}'/>
<ImageButton (...) android:onClick='#{() -> main.GotoPage(3,"three")}'/>
...
<ImageButton (...) android:onClick='#{() -> main.GotoPage(100,"one hundred")}'/>
</LinearLayout>
</layout>
and in MainActivity.java
public void GotoPage(int i, String otherVariable) {
/** code using i and otherVariable **/
}
UPDATE: For those who don't know how to set up data binding, I will explain it here so you don't have to google around for it. First, enable dataBinding in the build.gradle file:
android {
...
dataBinding {
enabled = true
}
...
}
Also, make sure jcenter() is in your repositories.
Then, go to the XML of the layout where onClick will be used and wrap its layout in a layout tag with a data section like this:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="main" type="com.example.android.yourapp.MainActivity" />
</data>
<YourLayout>
...
</YourLayout>
</layout>
For the variable tag's type parameter, you need to put the class that will contain the function which onClick points to. In this example, I will use the main activity class, which is named MainActivity in my test project.
After you have your layout wrapped in a layout tag like in the example above, clean the project in Android Studio. You may also need to invalidate cache/restart or close and reopen Android Studio.
Next, if the the layout with onClick you are trying to set up data binding for is the same layout set by setContentView in your main activity class, open the file that contains your main activity class. If the layout with onClick you are trying to set up data binding for is inflated programmatically in a different file, open the file in which the layout is inflated instead.
Add these imports to that file:
import com.example.android.yourapp.databinding.YourLayoutBinding;
import android.databinding.DataBindingUtil;
That first class you are importing is generated when you clean the project (and possibly have to invalidate cache/restart) and is automatically named after the XML file you added the layout wrapper to. If the layout file is named your_layout.xml, the import class will be named YourLayoutBinding. The exact import path will depend on your app name and structure, but it will always be within a databinding parent class.
The next step depends on whether the layout you are adding data binding to is set with setContentView or is inflated with inflate. Both versions of the following step make use of the method setMain. The setMain method is automatically generated and named using the value of the name parameter in the layout wrapper we added. Since we put name="main", the method is called setMain.
If the layout you are adding data binding to is the same layout set by setContentView find the line in your main activity class that looks like setContentView(R.layout.your_layout); and change it to use DataBindingUtil.setContentView instead of setContentView, adding this as its first argument. Use binding.setMain to point the layout's main variable to the current activity.
YourLayoutBinding binding = DataBindingUtil.setContentView(this, R.layout.your_layout);
binding.setMain(this);
If the layout you are adding data binding to is not set by setContentView but rather inflated go to where it is inflated in your code. It should look something like this:
return inflater.inflate(R.layout.your_layout, container, false);
Modify it to use DataBindingUtil.inflate, adding the previous inflater as its first argument. Use binding.setMain to point the layout's main variable to the main activity, and use binding.getRoot() to get the view. It should end up like this:
YourLayoutBinding binding = DataBindingUtil.inflate(inflater, R.layout.your_layout, container, false);
binding.setMain((MainActivity) getActivity());
return binding.getRoot();
Now the data binding is ready to use. Add a function for onClick to point to within your main activity class.
public void exampleFunction(int number, String text) {
System.out.println("Number: " + number + ", Text: " + text);
}
You can call it from the layout you added data binding to using a lambda expression. This example function doesn't require a View, so it can be used like this:
<Button android:id="#+id/buttonID"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="26sp"
android:text="Test"
android:onClick='#{() -> main.exampleFunction(123, "test")}'/>
Make sure to use single quotes around the value for onClick if you plan on using a String input.
If you do need to pass the button's view to your function, simply add a View parameter to your function's required arguments and use a lambda expression like this instead:
<Button android:id="#+id/buttonID"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textSize="26sp"
android:text="Test"
android:onClick='#{(view) -> main.exampleFunction(view, 123, "test")}'/>
If you will create some layout element in xml you can use there
<ImageButton
android:id="#+id/some_id_value" />
where some_id_value is kind of unique string which will be translate into id which is kept in R.java (better for you- don't change anything there) than in code you can get that id by using
R.id.some_id_value
read a little bit there that's really basics.
You can set Tags for a view. Tags are basically a way for views to have memories.
xml:
<ImageButton
...Other Parameters...
android:id="#+id/Button2"
android:tag="2"
android:onClick="GoToPageX"/>
<ImageButton
...Other Parameters...
android:id="#+id/Button3"
android:tag="3"
android:onClick="GoToPageX"/>
The line android:tag="2" set a tag value of 2(string data type) to Button2
Java file:
General Case:
Inside GoToPageX(View v) function,
use v.getTag() to get the tag value of corresponding view(From which ever view the method was called).
Your case:
Add the method as follows
public void GoToPageX(View v){
int i = Integer.parseInt(v.getTag()); //parseInt converts string to integer
startActivity(new Intent(getBaseContext(), activities.get(i)));
finish();
}

Categories

Resources