Only one of multiple views fails with findViewById() - java

I'm trying to reference a TextView from a layout. It works for all other views but for this specific one it returns null. Doing it the same way for all the views, so I can't see why it wouldn't work on this one.
Important to mention is that the layout file showed here is included into b_user_list. All views referenced from within the include works, except for one.
b_user_list.xml:
<include
android:id="#+id/showUserLay"
layout="#layout/c_show_front_layout"
... />
c_show_front_layout.xml:
<androidx.constraintlayout.widget.ConstraintLayout
android:id="#+id/volunDbListLay"
... >
<TextView
android:id="#+id/textView932"
... />
<View
android:id="#+id/divider522"
... />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/volunDbRecycler"
... />
<TextView
android:id="#+id/noVolunOrgsFound"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:gravity="center"
android:text="No organizations found."
app:layout_constraintBottom_toBottomOf="#+id/volunDbRecycler"
app:layout_constraintEnd_toEndOf="#+id/volunDbRecycler"
app:layout_constraintStart_toStartOf="#+id/volunDbRecycler"
app:layout_constraintTop_toTopOf="#+id/volunDbRecycler" />
</androidx.constraintlayout.widget.ConstraintLayout>
ActivityUser.java:
private ConstraintLayout settErrorLay;
private TextView errorTxt;
private TextView errorClose;
private TextView noVolunOrgsFound;
private RecyclerView volunDbRecycler;
private ConstraintLayout volunDbListLay, notVsAccWarning;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.b_user_list);
...
settErrorLay = findViewById(R.id.userIntegrErrorLay);
errorClose = findViewById(R.id.closeUserIntegrErrorTxt);
...
if (errorClose != null && settErrorLay != null) {
// Checks if includeView show_front_layout.xml worked
volunDbRecycler = findViewById(R.id.volunDbRecycler);
volunDbListLay = findViewById(R.id.volunDbListLay);
notVsAccWarning = findViewById(R.id.notVsAccWarning);
noVolunOrgsFound = findViewById(R.id.noVolunOrgsFound);
...
}
...
}
...
I found a way to reference the view, but obviously not the proper way. I ran through all the children of the parent view, and when the id match I add it as the view. Then it didn't return null:
if (noVolunOrgsFound == null) {
for (int i = 0; i < volunDbListLay.getChildCount(); i++) {
if (volunDbListLay.getChildAt(i).getId() == R.id.noVolunOrgsFound) {
noVolunOrgsFound = (TextView) volunDbListLay.getChildAt(i);
}
}
}
So I have ran out of ideas for why this doesn't work. It can't be the <include> since the other sibling views works, the id is the same both in the layout and java, and it works with by finding it within the children of the parent, just not with findViewById().
EDIT: I posted an answer that seemed to be solving the problem, but now I just have the same problem with a RecyclerView. This time, it didn't work to manually loop through the views of the parent and checking when the child view id match the RecyclerView's id..
Is there a limit to how many views can be loaded or referenced in Java or something?

Found a solution, I left out something in the question that I didn't think would have a say in the matter (And I'm still not sure why it does).
Before the findViewById(R.id.noVolunOrgsFound); was a function, and in that function was a button assigned to use the TextView (the one that was null). Not sure how the button makes the TextView null, but apparently it did.
initializeDbSettingsLay();
volunDbRecycler = findViewById(R.id.volunOrgRecycler);
volunDbListLay = findViewById(R.id.volunOrgListLay);
notVsAccWarning = findViewById(R.id.notVsAccWarning);
noVolunOrgsFound = findViewById(R.id.noVolunOrgsFound);
Moving the function after the findViewById(R.id.noVolunOrgsFound);, it now wasn't null anymore. Not sure why the button either made it null or not properly find the view, since it worked on the other layouts that also is used in that button function. Only the TextView became null. I'd like to say that the TextView can't be assigned after the ClickListener has been created, but generally values can be changed later, and so did the other views. So I'm not sure why it failed with the TextView.
Here is the function:
private void initializeDbSettingsLay() {
...
vsSettingsBtn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
userSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(Color.WHITE));
dbSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(Color.WHITE));
vsSettingsBtn.setBackgroundTintList(ColorStateList.valueOf(ActivityUser.this.getColor(R.color.slight_gray)));
userSettingsLay.setVisibility(View.GONE);
dbSettingsLay.setVisibility(View.GONE);
vsSettingsLay.setVisibility(View.VISIBLE);
learnMoreVsLay.setVisibility(View.GONE);
learnMoreVsBtn.setText("Learn more");
if (currentUserModel == null || currentUserModel.getVolunAccount() == null) {
volunDbListLay.setVisibility(View.GONE);
notVsAccWarning.setVisibility(View.VISIBLE);
} else {
volunDbListLay.setVisibility(View.VISIBLE);
notVsAccWarning.setVisibility(View.GONE);
loadVolunOrgList();
}
}
});
...
}
private void loadVolunOrgList() {
if (currentUserModel == null || currentUserModel.getVolunAccount() == null || currentUserModel.getOrgsList() == null || currentUserModel.getOrgsList().isEmpty()) {
return;
}
List<OrganisationModel> orgList = currentUserModel.getOrgDataList();
if (orgList.size() == 0) {
noVolunOrgsFound.setVisibility(View.VISIBLE);
return;
}
noVolunOrgsFound.setVisibility(View.GONE);
//Removing both noVolunOrgsFound.setVisibility() above from this function, the rest of the code then worked \/
UserIntegrAdapter orgAdapter = new UserIntegrAdapter(orgList, this, getSupportFragmentManager());
volunDbRecycler.setAdapter(orgAdapter);
volunDbRecycler.setLayoutManager(new LinearLayoutManager(ActivityUser.this));
}

Related

How to get period when data is loaded to stop shimmer?

I am using Volley for requests and want to add shimmer while data is loading. I can perform it with Handler but I don't know when data will come and can't set exact time.
Here is code in my Fragment. I want to set value false to variable isShimmer and it makes so fast so my shimmer is not running:
String url = "my_url";
JsonArrayRequest getArticleOfTheDayRequest = new JsonArrayRequest(Request.Method.GET, url, null,
response -> {
try {
for(int i = 0; i < response.length(); i++){
JSONObject articleObject = response.getJSONObject(i);
int article_id = articleObject.getInt("article_id");
String title = articleObject.getString("title");
String text = articleObject.getString("text");
ArticleOfTheWeek articleOfTheWeek = new ArticleOfTheWeek(article_id, title, text);
articleOfTheWeekList.add(articleOfTheWeek);
}
articleOfTheWeekAdapter = new ArticleOfTheWeekAdapter(getContext(), articleOfTheWeekList);
recyclerView.setAdapter(articleOfTheWeekAdapter);
articleOfTheWeekAdapter.isShimmer = false;
articleOfTheWeekAdapter.notifyDataSetChanged();
} catch (JSONException e) {
e.printStackTrace();
}
},
error -> Log.d("Error.Response", error.getMessage() + " ")
);
Here code in my adapter:
public boolean isShimmer = true;
int shimmerNumber = 2;
#Override
public void onBindViewHolder(#NonNull final ArticleOfTheWeekViewHolder holder, int position) {
final ArticleOfTheWeek articleOfTheWeek = articleOfTheWeekList.get(position);
if(isShimmer)
{
holder.shimmerFrameLayout.startShimmer();
}else
{
holder.shimmerFrameLayout.stopShimmer();
holder.shimmerFrameLayout.setShimmer(null);
holder.textViewTitle.setBackground(null);
holder.textViewTitle.setText(articleOfTheWeek.getTitle());
holder.textViewDesc.setBackground(null);
holder.textViewDesc.setText(Html.fromHtml(articleOfTheWeek.getText()));
}
}
As you see if I set isShimmer true, shimmer runs infinite. So I can't get a period when all data is loaded to change value of isShimmer
I've used this library for show shimmer effect while fetching data. First set visibility true to your shimmer layout in your xml file then start shimmer on the start of the activity shimmerViewContainer.startShimmer().
Set shimmerViewContainer.stopShimmer() in your volley method after set data to your adapter in activity file.
As well as stop shimmer on onPause() and onDestroy()
May be it would be easier for you to maintain the shimmer state if you put the shimmer in the parent layout rather than within the adapter layout. I would prefer below approach:
1. Inside the layout of Activity/Fragment
<RelativeLayout>
<RecyclerView/>
<FrameLayout
android:id="#+id/shimmer_container">
<!-- show hide this FrameLayout container depend on state of data loading/loaded -->
<include layout="#layout/list_shimmer"/>
</FrameLayout>
</RelativeLayout>
The list_shimmer may be looks like:
<com.facebook.shimmer.ShimmerFrameLayout 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="match_parent"
app:shimmer_auto_start="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<include layout="#layout/just_like_adapter_item_layout" />
<include layout="#layout/just_like_adapter_item_layout" />
<!-- repeat until it's enough to fill the screen -->
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
The important property:
app:shimmer_auto_start="true"
So you don't need to start the shimmer programmatically.
2. Activity/Fragment
void initList(List<Data> items) {
adapter.setItems(items);
shimmer_container.setVisibility(View.GONE);
}

Position in recylerview changes while scrolling

Seems like position in recylerview changes while scrolling.
What I want to do is like this.
Adapter.java
#Override
public void onBindViewHolder(aViewHolder holder, int position) {
if (position == 0) {
holder.zeroIcon.setVisibility(View.VISIBLE);
} else if (position == 1) {
holder.oneIcon.setVisiblity(View.VISIBLE);
} else {
holder.otherIcon.setVisiblity(View.VISIBLE);
}
// Set text on each item
...
}
#Override
public int getItemCount() { return models.size(); }
public class aViewHolder extends RecyclerView.ViewHolder {
private ImageView zeroIcon;
private ImageView oneIcon;
private ImageView otherIcon;
public aViewHolder(View itemView) {
super(itemView);
zeroIcon = itemview.findViewById(...);
...
}
}
I set these icon's visibility GONE as default in xml file.
When I see the recylerview at first, the icons show up as I expected depending on its position.
However, when I scroll down and scroll up, incorrect icons also show up on incorrect position.
Like otherIcon shows up on the first and second item while scrolling down and up. While scrolling down, zeroIcon and oneIcon show up on some other items.
How can I fix this?
list_item.xml is like this.
<RelativeLayout ...>
<ImageView
android:id="#+id/zero"
android:visiblity="gone"
android:background="#drawable/zero" />
<ImageView
android:id="#id/one"
android:visiblity="gone"
android:background="#drawable/one" />
<ImageView
android:id="#id/other"
android:visiblity="gone"
android:background="#drawable/other" />
Modify it in this way,
if (position == 0) {
holder.zeroIcon.setVisibility(View.VISIBLE);
holder.otherIcon.setVisiblity(View.GONE);
holder.oneIcon.setVisiblity(View.GONE);
} else if (position == 1) {
holder.oneIcon.setVisiblity(View.VISIBLE);
holder.zeroIcon.setVisibility(View.GONE);
holder.otherIcon.setVisiblity(View.GONE);
} else {
holder.otherIcon.setVisiblity(View.VISIBLE);
holder.oneIcon.setVisiblity(View.GONE);
holder.zeroIcon.setVisibility(View.GONE);
}
In RecyclerView you should manage other views also while changing an item.

Section in RecylerView with Cursor

I've read a lot of StackOverflow posts about such problem but most of them were not using cursor as their data source for RecyclerView. But my app does.
In my app, I query the database which gives me results as shown in the following image. Results are ordered by _id column.
I want my RecyclerView to represents the data something like below:
White Blood Cell(WBC)
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
Red Blood Cell - Male
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
52 10^9/L 2018-08-15
As you can see it forms groups with headings and relevant items go under each heading.
I have created two different layouts.
Heading with an item below it layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<TextView
android:id="#+id/heading"" />
<LinearLayout>
<TextView
android:id="#+id/value" />
<TextView
android:id="#+id/date" />
</LinearLayout>
</LinearLayout>
Item layout
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout>
<TextView
android:id="#+id/value" />
<TextView
android:id="#+id/date"/>
</LinearLayout>
Ignore missing details in layout files
On the other hand, my adapter does the following.
public class BloodTestRecordAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
// it holds the _id of the last group
private int lastGroupId = -1;
private final int VIEW_TYPE_HEADING = 0, VIEW_TYPE_ITEM = 1;
#Override
public int getItemViewType(int position) {
int returnItemViewType;
// get _id value for the row at given position
int currentGroupId = getCursorAtPosition(position).getInt(0);
if (currentGroupId != lastGroupId) {
// return VIEW_TYPE_HEADING
lastGroupId = currentGroupId;
returnItemViewType = VIEW_TYPE_HEADING;
} else {
returnItemViewType = VIEW_TYPE_ITEM;
}
return returnItemViewType;
}
#Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RecyclerView.ViewHolder viewHolder;
View view;
if (viewType == VIEW_TYPE_HEADING) {
// inflate HEADING LAYOUT
} else {
// inflate item layout
}
return viewHolder;
}
#Override
public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, Cursor cursor, int position) {
if (viewHolder.getItemViewType() == VIEW_TYPE_HEADING) {
// bind values to heading layout
} else {
// bind values to item layout
}
}
public class HeadingViewHolder extends RecyclerView.ViewHolder {
}
public class ItemViewHolder extends RecyclerView.ViewHolder {
}
}
what I am doing in getItemViewType is I check to see whether the _id column matches the lastGroupId by using position argument that is passed to the method. If it matches I just return the VIEW_TYPE_ITEM, which is the normal item layout.
And if it doesn't match, then it gives us an indication that it is the beginning of another group, so for this case, I return VIEW_TYPE_HEADING.
It works fine but as I get more items the heading and placement of items under groups don't seem to be correct. Sometimes the heading layout goes vanish for some group of items as shown below in the image.
I know for sure that the way i implemented the getViewItemType is not
correct, I also know as my viewholders get recycled it messes with my getViewItem method because of that lastGroupId variable.
Can you give me any suggestions how should I do this or what can I do to achieve such things in RecyclerView with Cursor or what are my mistakes?
The problem appear when you scrolling up the recyclerView and so that lastGroupId doesn't full fill correctly.
Use this logic in place of your current code in getItemViewType:
if (position == 0) {
return VIEW_TYPE_HEADING;
} else {
int currentGroupId = getCursorAtPosition(position).getInt(0);
int previousGroupId= getCursorAtPosition(position - 1).getInt(0);
return currentGroupId != previousGroupId ? VIEW_TYPE_HEADING : VIEW_TYPE_ITEM;
}

Building a variable upon buttons to trigger listeners

I am breaking my head dealing with variables types in my app.
I try in a loop to increase a var and pass it to a listener, according to the name of the buttons defined in my XML layout.
I would like to start from "jeton1" to "jeton2","jeton3"...., but cannot manage to do that in my code (errors arising), the vars do not point to the buttons stored in the XML and not showing up when calling the buttons listeners.
I made a test with a defined array but the stuff failed.
Test code below made upon only one button.
A help would be greatly appreciated.
Here is my code
The XML layout :
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="20"
android:columnCount="9">
<Button
android:id="#+id/jeton1"
android:layout_column="0"
android:layout_row="0"
android:text="#string/boutona"
android:layout_height="88dp"
android:layout_width="88dp"
android:textSize="40sp"
android:backgroundTint="#eeceac"
android:textStyle="bold" />
Main Java :
public class MainActivity extends AppCompatActivity {
int i;
String jeton = "";
#SuppressLint("ClickableViewAccessibility")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Main loop
for (i = 1; i < 2; i++) {
jeton = "jeton" + i; // Should throw "jeton1", "jeton2".....
final Button jetonnew;
jetonnew = (Button) findViewById(R.id.jeton); // Error 'cannot resolve symbol
// jetonnew = (Button)findViewById(R.id.jeton+i);
// Step 4 Listener
jetonnew.setOnTouchListener(
new View.OnTouchListener() {
#Override
public boolean onTouch(View view, MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
jetonnew.getBackground().setAlpha(0); // Crash app
jetonnew.setTextColor(Color.parseColor("#2aa17b"));
break;
}
return true;
}
});
}
}
}
Many thanks for your replies and suggestions.
If you have a fixed number of buttons, you can store an array of integers
int[] ids = {R.id.button1, R.id.button2, ...};
However, if you want to dynamically add buttons, you should try creating them programmatically
Button newButton = new Button(this);
or you can create some custom layout and inflate it
inflate(this, R.layout.customLayout, null);
Keep in mind that R.id.someId returns and integer not a string so you cannot append to it. Also adding a string after id does not work for the same reason.
jetonnew = (Button)findViewById(R.id.jeton+i);
This portion of code does not work because R.id.jeton is a generated integer not a string.
You should consider using findViewByTag instead of findViewById. Your code will look like this:
<GridLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:rowCount="20"
android:columnCount="9">
<Button
android:tag="jeton1"
android:layout_column="0"
android:layout_row="0"
android:text="#string/boutona"
android:layout_height="88dp"
android:layout_width="88dp"
android:textSize="40sp"
android:backgroundTint="#eeceac"
android:textStyle="bold" />
So in your java file:
for (i = 1; i < 2; i++) {
jeton = "jeton" + i; // Should throw "jeton1", "jeton2".....
final Button jetonnew;
//findViewByTag here
jetonnew = (Button) findViewByTag(jeton)
Another way is to just iterate through GridLayout child, like this:
//suppose your gridLayout has id=#+id/gridparent
GridLayout gridParent = (GridLayout)findViewById(R.id.gridparent);
for(int index=0; index<gridParent.getChildCount(); ++index) {
Button nextButton = (Button)gridParent.getChildAt(index);
//attach listener here
}
You should create arrays of your grid button id's.
#IntegerRes int [] resourceButtonIds = new int[]{R.id.jeton1,R.id.jeton2};
Then modify the loop accordingly.
for (i = 0; i < resourceButtonIds.length; i++) {
final Button jetonnew;
jetonnew = (Button) findViewById(resourceButtonIds[i]);
// now set all your listeners
}
If you want to find the id of the button by using its name:
jetonnew = findViewById(getResources().getIdentifier("jeton" + i, "id", getPackageName()));

My calculator android application crashed after i added the function given below

Here is my xml code
<Button
android:id="#+id/buttonSign"
style="?android:attr/buttonStyleSmall"
android:layout_width="0dp"
android:layout_weight="0.166"
android:text="±"
android:onClick="ProcessSignInput" />
Here is my java code
public void ProcessSignInput(View v)
{
Button btn = (Button) v;
if(inputText.length() > 0 && currentInput!="0")
{
if(currentInput.charAt(0) == '-')
{
inputText.setText(currentInput.subSequence(1, inputText.length()));
}
else
{
inputText.setText("-" + currentInput.toString());
}
}
}
Here is the line for current input
String currentInput = inputText.getText().toString();
Every thing was working perfecctly before i added these lines.Nw app just crashes can anyone explain what is causing this crash thanx in advance.
No you can't write that code outside your on create method just at the beginning of extends activity method
You have to call this on Click event of your Button. Use this. If you are calling this it outside of your onCreate() method
String currentInput = inputText.getText().toString();
It will be always null because at that time you din't find ID for your EditText or TextView UI element and that value or string contains null data. So it will be always null pointer exception.
public void ProcessSignInput(View v)
{
Button btn = (Button) v;
String currentInput = inputText.getText().toString();
if(inputText.length() > 0 && !currentInput.equalsIgnoreCase(0))
{
if(currentInput.charAt(0) == '-'))
{
inputText.setText(currentInput.subSequence(1, inputText.length()));
}
else
{
inputText.setText("-" + currentInput.toString());
}
}
}
Also don't compare String using ==, use
equals() or equalsIgnoreCase()

Categories