Java Android Badge Drawable not working properly after configuration change - java

I have a Bottom Navigation view with a badge drawable that shows new chats.
This badges are updated upon a listener to Firebase database, where I store the notification counter. When this value change, the badge is updated. Moreover if the counter is equal to zero, the badge is set not visible.
Everything works fine except if I change some configuration using the device Settings (such as language or removing permissions). In fact, if I do that and go back to the app, the activity is re-created (sometimes without destroying it) and badge reloaded. But the setVisibility seems not working. Even if the counter is zero the badge is visible. Plus is not update anymore when the listener is triggered.
The code works, I checked with some logs if the listener is triggered and if the lines which include setVisibility are run. It just seems to have random behaviour.
If the activity is destroyed and recreated again, it works.
Any help will be appreciated!
this is how I initialize the badge
bottomNav = findViewById(R.id.bottom_navigation);
badge_chat = bottomNav.getOrCreateBadge(R.id.nav_chat);
badge_chat.setVisible(false);
this is the listener code
public void onChildChanged(#NonNull DataSnapshot dataSnapshot, #Nullable String s) {
int badge_counter = dataSnapshot.getValue(int.class);
boolean visibility;
if (badge_counter == 0) {
visibility = false;
} else {
visibility = true;
}
badge_chat.setNumber(badge_counter);
badge_chat.setVisible(visibility);
}

One way I've managed to resolve this is to create/remove badge whenever it needs to be visible/hidden. In your case, something like this should work:
if (badge_counter == 0) {
getBadge(R.id.nav_chat)?.isVisible = false
removeBadge(R.id.nav_chat)
} else {
getOrCreateBadge(R.id.nav_chat).apply{
isVisible = true
number = badge_counter
}
}
note: the answer is in Kotlin.

Related

The animation for my programatically created button acts weird in my application

So I am facing a weird bug I cannot explain - I cannot even reproduce it sometimes.
Basic context:
I have an application, which lists objects. Every object has a name and a point value. For every object, the addCustomSpinner function creates a "ticket" (a custom view, kind-of-spinner) and shows them in a scrollview so the user can select the one needed. There are four different 'containers' for four different kind of objects - so the layout can be populated with four kind of "ticket" package.
The data for the objects are collected from a database. The addCustomSpinner is called with a for cycle for every object in the database, and - Important - before the for method, the Layout it populates with the tickets is cleared (removeAllViews).
Inside addCustomSpinner, everything is created as "new" - like the button in question.
addCustomSpinner creates this button and adds a new onClickListener. Inside onClickListener, a new boolean is created - this is used to show a different animation when the button is clicked again. On first click (boolean = true), the arrow turns 180 degrees and faces upwards, on second click (boolean = false) the arrow turns 180 degrees and faces downwards. Works like a charm, until...
The bug I am facing:
Sometimes - as I already mentioned, not every time - if I click the button for one "ticket", then leave it 'opened' and click on an another one, and leave it 'opened' also, THEN I choose to populate the layout with a different kind of "ticket" package - The arrow faces upwards by default on every ticket in every package! Sometimes - again, just sometimes - with the same pattern I can turn it back, but it happens just "by accident".
I don't understand how the animation and state of the buttons can be connected, if every created ticket is new, every button is new, every onClickListener is new, and every boolean inside onClickListener is new. And if these are connected somehow, then why can that be that every behavior is "unique" for the buttons, nothing else shows any connection - even this is just a "sometimes" bug, a pretty rare one.
Can anybody help me why this happens?
What I tried:
Well, tried to trace the issue - but since it happens just by accident, I have no clue, I just searched if I can do anything else than the boolean to add different animation for the clicks. Sadly using ObjectAnimator is not a good solution for me - not the same result at least, since my animated arrow not only rotates, but it also changes its color. Shapeshifter seemed like a good idea to create animations easily, but now as I see it, maybe a simple rotation will be my ultimate solution.
Here's the code for the button:
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
EDIT:
The full addCustomSpinner():
private void addCustomSpinner(Routes mRouteItemToAdd, String placeName) {
//creating a new View for my custom layout created in xml
View customRoutesView = new View(this);
LinearLayout.LayoutParams customViewParams = new LinearLayout.LayoutParams(
ViewGroup.LayoutParams.WRAP_CONTENT,
ViewGroup.LayoutParams.WRAP_CONTENT
);
customRoutesView.setLayoutParams(customViewParams);
customRoutesView = LayoutInflater.from(this).inflate(
R.layout.custom_view_layout, routeLayout, false
);
//Setting up the views inside the custom view
ImageView imageViewDiffImage = customRoutesView.findViewById(R.id.routeDiffImageView);
TextView textViewRouteName = customRoutesView.findViewById(R.id.routeNameTextView);
TextView textViewRouteDiff = customRoutesView.findViewById(R.id.routeDiffTextView);
ImageButton customButton = customRoutesView.findViewById(R.id.customButton);
RadioButton climberNameOne = customRoutesView.findViewById(R.id.climberNameOne);
RadioButton climberNameTwo = customRoutesView.findViewById(R.id.climberNameTwo);
Button climbedItButton = customRoutesView.findViewById(R.id.climbed_it_button);
RadioGroup climberNameRadioGroup = customRoutesView.findViewById(R.id.climberNameRadioGroup);
RadioGroup climbingStyleRadioGroup = customRoutesView.findViewById(R.id.styleNameRadioGroup);
RelativeLayout routeWhoClimbed = customRoutesView.findViewById(R.id.routeWhoClimbedRelativeLayout);
imageViewDiffImage.setImageResource(R.mipmap.muscle);
textViewRouteName.setText(mRouteItemToAdd.name);
textViewRouteDiff.setText("Difficulty: " + (int) mRouteItemToAdd.difficulty);
climberNameOne.setText(climberName1);
climberNameTwo.setText(climberName2);
routeWhoClimbed.setVisibility(GONE);
//Here comes the button with the animated image
customButton.setOnClickListener(new View.OnClickListener() {
boolean isCustomButtonClicked = true;
#Override
public void onClick(View v) {
if (isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue_back);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(View.VISIBLE);
isCustomButtonClicked = false;
} else if (!isCustomButtonClicked) {
customButton.setImageResource(R.drawable.avd_anim_arrow_blue);
Drawable d = customButton.getDrawable();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (d instanceof AnimatedVectorDrawable) {
animArrowAnim = (AnimatedVectorDrawable) d;
animArrowAnim.start();
}
}
routeWhoClimbed.setVisibility(GONE);
isCustomButtonClicked = true;
}
}
});
//Button, works like an 'OK' or something, and I have no
//problem with this
climbedItButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
int checkedNameButton = climberNameRadioGroup.getCheckedRadioButtonId();
int checkedStyleButton = climbingStyleRadioGroup.getCheckedRadioButtonId();
RadioButton checkedNameRadioButton = (RadioButton) findViewById(checkedNameButton);
RadioButton checkedStyleRadioButton = (RadioButton) findViewById(checkedStyleButton);
String checkedName = (String) checkedNameRadioButton.getText();
String checkedStyle = (String) checkedStyleRadioButton.getText();
addClimbToDatabase(user.getUid(), checkedName, mRouteItemToAdd, placeName, checkedStyle);
}
});
//And finally, I add this new "ticket" with the custom view to the layout i want to show it. Again, this also works like a charm, no problem here.
routeLayout.addView(customRoutesView);
}
Ultimately, I did not manage to understand the problem throughly, but I was able to eliminate it.
So during my fixing tries I narrowed down the problem to the animated drawable state - credit to #avalerio for his pro tip, but the answer wasn't addig an id to the button. I think somehow and sometime, the state of the first animation (turning the arrow 180 degrees) stuck in the end position - causing the other views using this animatedDrawable showing it in end position on start.
.reset() did not help, since it resets the animatedVectorDrawable object, not the animation xml drawable state. My solution is a kind of workaround, but it is working: when the custom-view 'ticket' is created with the animated-drawable-imagebutton, I set the imageResource of the button to a not-animated xml drawable - this drawable is basically the start position of my animated-drawable. This way, when the 'tickets' are generated, the imagebutton is 'hardcoded' in the start position.
Not elegant, but works. BUT(!) I would really appreciate if someone could explain to me how this weird behavior is possible - just sometimes, randomly, with no pattern I can reproduce intentionally.

SharedPreferences String Set data lost after app kill (android emulator)

I am running this on an emulator: 5554:Nexus_5_API_22_x86.
I am trying to learn SharedPreferences and have written a simple test program.
It contains two buttons: one adds a String + random # to a set which will be stored in SharedPreferences, and the other prints the contents of that set.
Whenever I press the square button on the bottom right hand of the screen and press 'x' to close the app window, then relaunch the app, the contents of the set are reset - in other words, printing the set yields nothing.
However, if I exit the app using only the back button, the contents remain - in other words, printing the set yields whatever was in it before.
Java:
...
public class MainActivity extends AppCompatActivity
{
final int PREF_MODE_PRIVATE = 0;
TextView output;
Set<String> testSet;
Random randomGenerator = new Random();
SharedPreferences data;
SharedPreferences.Editor dataEditor;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
output = (TextView) findViewById(R.id.textView); //getting the output textfield
data = getPreferences(PREF_MODE_PRIVATE);
//If first-time setup has not been completed, execute the following block
//I don't want the String Set to be reset to empty every time the app is launched
if(data.getBoolean("initialized", false) == false)
{
//Adding the empty set to storage
testSet = new HashSet<String>();
dataEditor = data.edit();
dataEditor.putStringSet("testSet", testSet); //Add the empty Set to storage
dataEditor.putBoolean("initialized", true); //Set initialized flag to true
dataEditor.apply();
}
}
public void printTestSet(View view)
{
output.setText(""); //Clears the text field
Set<String> toBePrinted = data.getStringSet("testSet", null); //Gets the String Set
//Prints content of the String Set
if(toBePrinted != null)
{
for(String word : toBePrinted)
{
output.append(word + '\n');
}
}
}
public void addToTestSet(View view)
{
//Generate a string followed by a random number and add it to the String Set
int randomInt = randomGenerator.nextInt(1000);
data.getStringSet("testSet", null).add("NEW STRING #" + randomInt);
}
}
The button that prints the String Set calls printTestSet and the one that adds a String to the Set calls addToTestSet.
Upon creation, the app uses a simple boolean to check if it has been initialized the for the first time. If not, it adds an empty String Set to storage and sets the boolean to true. If the boolean is already true (meaning it has already added the empty string set), that step is skipped.
You need to either commit your data either realtime (where you are doing apply) or in application life cycle handler onpause (when your app goes to background). Use option 1 when you have little data or 2 when you have large amount of data to commit.
It looks like you're not saving the shared preferences in addToTestSet.
When you do getStringSet and then add, you need to again save the string Set back into shared prefs like you do in your onCreate() using dataEditor.apply().
Or if you want to be a bit more efficient, you can save your stringSet in the activity's onPause() method to prevent constantly writing to SharedPrefs
When you hit back, your app process isn't being killed, which means when you open it up again the Android system is restoring whatever it can (what was written in textViews, checkboxes that were checked, etc. simple things like that). What you see in the box might not actually be getting populated by SharedPrefs.

How do you iterate through dynamically created objects?

Lets say you have an object like this and you want to have the program go through each dynamically created Checkbox to see if it has not been checked.
If has not been checked, then the program should create a notification alerting the user that one or more of these objects has not been checked.
What is the best way to have the program identify whether the checkbox is checked or not?
Each time I run the program, it only applies to the last created Checkbox regardless of how many checked or unchecked checkboxes proceed it.
Thank you for your time.
View ObjectView;
CheckBox check;
//A whole bunch of code here.
public void onClick(View arg0) {
if (check==null){
}
else if (check==null || check.isChecked()){
}
else {
ObjectView.getId();
NotificationCompat.Builder mBuilder =
new NotificationCompat.Builder(getActivity())
.setSmallIcon(android.R.drawable.stat_notify_more)
.setContentTitle("Items missing")
.setContentText("One or more items are missing");
int ID_Notify = 01;
getActivity();
NotificationManager managenote = (NotificationManager)getActivity().getSystemService(Context.NOTIFICATION_SERVICE);
managenote.notify(ID_Notify, mBuilder.build());
Im gonna help you with an example of my code and try to explain it, aware that im not gonna babysit you (means you cant just copy paste) because i still have some work to do.
First, a new dynamic spinner will be created everytime you click a button (inside onClick) :
Spinner spinner = new Spinner(this);
spinner.setAdapter(spinChildAdapter);
parentSpinner.addView(spinner);
spinner.setId(totalDynamicChild); //the spinner's id will be the increment from 0
spinnderIdList.add(totalDynamicChild); //list of the dynamic spinner ID
totalDynamicChild++;
Then, we can access those dynamic Spinners with :
for(int i = 0; i < totalDynamicChild; i++)
{
Spinner s = (Spinner)findViewById(spinnderIdList.get(i));
//do something with the spinner's object here
}
Feel free to comment if you have some questions.

Android beginner onClick crash

I'm programming Android for the first time and I'm having some difficulties. The idea is to make a guessing game app in which the user takes a number in his/her head and the app tries to guess it. The user will give hints like higher and lower to the app. For some reason the app crashes after I press the start button. Because of this, I know that there is an error in the onClick method but since it shuts down immediately after I press the start button, I can't use something like a println to debug.
So actually I have 2 questions:
Where does my reasoning fail? (or show me how to figure out my mistakes)
How can I debug things like this?
The start, higher and lower are all buttons in the program.
#Override
public void onClick(View arg0) {
int min = 0;
int max = 100;
Random random = new Random(100);
int answer = 0;
if (arg0 == start) {
answer = random.nextInt(100);
buttonTextView.setText(answer);
}
else if (arg0 == higher){
min = answer;
answer = random.nextInt((max - min) + min);
buttonTextView.setText(answer);
}
else if (arg0 == lower) {
max = answer;
answer = random.nextInt((max-1) - min);
buttonTextView.setText(answer);
}
}
where does my reasoning fail?
You are using the wrong setText() method. In the TextView Docs you will see that there is one which takes an int, this is for retrieving a String resource that you have in your strings.xml so you would pass it a resource id. So your setText() is looking for a resource with the id of whatever your answer variable is. You will want to convert this to a String with something like
buttonTextView.setText(String.valueof(answer));
or one of several different ways.
How can I debug things like this?
When your app crashes there will be an exception in your logcat. This answer can help you to read your logcat. To open your logcat window in Eclipse, if it isn't already, you can do this
Window --> Show View --> Other --> Android --> LogCat
A couple side notes
You should change your params like in onClick() to something meaningful so I would change
public void onClick(View arg0)
to something like
public void onClick(View v) // v for view, could also be view, btn
// whatever makes sense to you and others who may read it
You also should compare the id of your View clicked instead of the View itself. So you would compare it with something like the following (assuming you changed arg0 to v)
if (v.getId() == R.id.start) // Assuming start is the id in your xml of your Button
// this will also allow you to use a switch statement
Your variables in onClick() (min, max, and answer) should be initialized outside of onClick() or they will be reset to the default values with each click which I'm pretty sure you don't want (thanks to 323go for pointing that out).

Counting how many times my Android app has been opened

I am developing an android app and I want to know how many times it has been opened. Is there a way to do this?
The problem with using onCreate in an Activity is that this will increment the counter even on orientation changes. Using onCreate in an Application also has a downside in that your counter will only be incremented when after the VM has closed - so even if the app exits and reopens this will not necessarily increment.
The truth is there is no fool-proof method for handling this sort of count, however I have come up with a very good way of doing this, that is about as close to 100% accurate as possible. It requires work in both an Application class and your main Activity class, and relies on timestamps to differentiate between orientation changes and actual app launches. To start, add the following Application class:
/**
* Application class used for correctly counting the number of times an app has been opened.
* #author Phil Brown
* #see Stack Overflow
*
*/
public class CounterApplication extends Application
{
private long lastConfigChange;
/** #param buffer the number of milliseconds required after an orientation change to still be considered the same event*/
public boolean wasLastConfigChangeRecent(int buffer)
{
return (new Date().getTime() - lastConfigChange <= buffer);
}
#Override
public void onCreate()
{
lastConfigChange = new Date().getTime();
}
#Override
public void onConfigurationChanged(Configuration newConfig)
{
super.onConfigurationChanged(newConfig);
lastConfigChange = new Date().getTime();
}
}
You should add this Application to your AndroidManifest.xml by specifying the name application attribute:
android:name="path.to.CounterApplication"
Now, in your main Activity, add the following in onCreate:
//note that you can use getPreferences(MODE_PRIVATE), but this is easier to use from Fragments.
SharedPreferences prefs = getSharedPreferences(getPackageName(), MODE_PRIVATE);
int appOpenedCount = prefs.getInt("app_opened_count", 1);
if (!((CounterApplication) getApplication()).wasLastConfigChangeRecent(10000))//within 10 seconds - a huge buffer
{
appOpenedCount += 1;
prefs.edit().putInt("app_opened_count", appOpenedCount).commit();
}
//now say you want to do something every 10th time they open the app:
boolean shouldDoThing = (appOpenedCount % 10 == 0);
if (shouldDoThing)
{
doThing();
appOpenedCount += 1;
//this ensures that the thing does not happen again on an orientation change.
prefs.edit().putInt("app_opened_count", appOpenedCount).commit();
}
Just, declare:
private SharedPreferences prefs;
private SharedPreferences.Editor editor;
private int totalCount;
Initialize in onCreate(...) :
prefs = getPreferences(Context.MODE_PRIVATE);
editor = prefs.edit();
Print or count wherever you want (any where in onCreate or any specific click as you specified)
totalCount = prefs.getInt("counter", 0);
totalCount++;
editor.putInt("counter", totalCount);
editor.commit();
Now print totalcount where you want to count eg.:
System.out.println("Total Application counter Reach to :"+totalCount);
In your Application or Activity's onCreate() method, increment a counter stored in persistent storage such as SharedPreferences.
You can use shared preferences. Every time the app is opened, retrieve the preferences, increment the count, then store it right away. The only issue is that if a user deletes the app along with all preferences, then the count will be erased too. Here is an example of committing to the preferences. Use getPreferences to retrieve them at the startup of the app.
SharedPreferences prefs=getPreferences(Context.MODE_PRIVATE);
SharedPreferences.Editor editor=prefs.edit();
editor.putString("pref 1", "some text");
editor.commit();
1. For a simple approach, keep a text file where you increment the value by 1, after reading it. Keep the count increment on OnCreate() method of Activity
2. You can use SharedPreference.
3. Well DataBase can also be used...but i think that too over-kill for this....
One way:
Keep a property in preferences and on launching activity update preference count by '1', but you may not be able to see this increased value because it stays on phone.
Otherway
Call a service to your server (if you have any) to increment the visit count.
Using SharedPreference or the Database.
during OnCreate add 1 to the numberofTimes counter and commit.
OnCreate (Bundle bundle){
mPref = getPreferences();
int c = mPref.getInt("numRun",0);
c++;
mPref.edit().putInt("numRun",c).commit();
//do other stuff...
}
OnCreate is called regardless of you start the app or you resume the app, but isFinishing() returns true if and only iff the user (or you) called finish() on the app (and it was not being destroyed by the manager)
This way you only increment when you are doing fresh start.
the onFinishing() Method inside of a OnPause method to check to see if the activity is being finish() or just being paused.
#Override
protected void OnPause(){
if(!onFinishing()){
c = mPref.getInt("numRun",0);
c--;
mPref.edit().putInt("numRun",c).commit();
}
//Other pause stuff.
}
As I have said in another answer, I think the following is the best solution:
private static boolean valueOfLaunchCountModified = false;
#Override
protected void onCreate(Bundle savedInstanceState) {
if(!valueOfCountModified){
preferences = getPreferences(MODE_PRIVATE);
launchCount= preferences.getInt("launchCount", 0);
if(preferences.edit().putInt("launchCount", ++launchCount).commit()){
valueOfCountModified = true;
}
}
if(launchCount == 5 && valueOfCountModified){
//Do whatever you want
}
}
If we remember the definition of a static variable, we will discover that is perfect for us:
They are associated with the class, rather than with any object. Every instance of the class shares a class variable.
When onPause method or an orientation change is executed the value of valueOfLaunchCountModified doesn't change; however, if the app process is destroyed, the value of valueOfLaunchCountModified changes to false.

Categories

Resources