Reputation: 12837
This problem is causing me to doubt my sanity. I think I have hit on an android bug and I need a work around alternative solution to NumberFormat.getIntegerInstance().format.
I'm having problems getting formatted numbers to display in a TextView It's an intermittent problem but is unreliably reproducable.
I have a method that calculates a balance every time a spinner item is selected via a call back event from a fragment. There are 6 spinners and it doesn't matter which is selected
Sometimes it can take 30 or 40 clicks (selections on the spinners) for the problem to appear and other times only 10 or 12 clicks. The issue is totally unrelated to the actual value and the calcBalance method ALWAYS returns a formatted integer. It's just that sometimes that integer is displaying as blank text in the TextView.
The thing is that the setText method is working it's only the numbers that don't display.
Once the problem manifests itself it will happen nearly every selection Switching between landscape and portrait seems to clear the problem but it will start again
The calcBalance method looks like this
public String calcBalance() {
int balance = 90000000;
for(int i = 0; i < this.cars.size(); i++){
balance = balance - this.cars.get(i).getDriver().getPrice();
balance = balance - this.cars.get(i).getChassis().getPrice();
balance = balance - this.cars.get(i).getEngine().getPrice();
Log.i("@@@@", "Calculating balance: " + balance);
}
return NumberFormat.getIntegerInstance().format(balance);
}
The method that calls this balance complete with debug messages looks like this
@Override
public void onBalanceChanged(int position, CarModel car) {
// Try to change the car but if it has not already been added then add it instead using outOfBounds
// exception to check if item exists
mi ++;
try {
mTeam.cars.set(position, car);
}catch(java.lang.IndexOutOfBoundsException e){
mTeam.cars.add(car);
}
if (mTvBalance == null){
mTvBalance = (TextView) findViewById(R.id.lblBalance);
}
String balance = mTeam.calcBalance();
Util.log_debug_message("Setting text Balance is " + balance);
String balance_text = mi + " Available Balance: $" + balance;
Util.log_debug_message("Balance text is " + balance_text);
mTvBalance.setText(balance_text);
}
An example log output states the the value of the text I am setting is
Balance text is 25 Available Balance: $10,000,000
Yet the text view shows only
25 Available Balance:
The number 25 is just part of my debugging to prove that the TextView.setText was actually getting set and it's just the numbers that aren't displaying.
The log outputs that resulted in the TextView showing just text without the value (includes the example above are as follows
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@4514fe58
I/@@@@ ( 1249): Calculating balance: 45000000
I/@@@@ ( 1249): Calculating balance: 0
D/QuizApp ( 1249): Setting text Balance is 0
D/QuizApp ( 1249): Balance text is 24 Available Balance: $0
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@450f3ec0
I/@@@@ ( 1249): Calculating balance: 45000000
I/@@@@ ( 1249): Calculating balance: 10000000
D/QuizApp ( 1249): Setting text Balance is 10,000,000
D/QuizApp ( 1249): Balance text is 25 Available Balance: $10,000,000
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@4517e3d0
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@451c4b70
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@4513fef8
I/@@@@ ( 1249): Calculating balance: 45000000
I/@@@@ ( 1249): Calculating balance: 13000000
D/QuizApp ( 1249): Setting text Balance is 13,000,000
D/QuizApp ( 1249): Balance text is 26 Available Balance: $13,000,000
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@45115e48
And some log output of the times where I DO get the correct text set in the TextView
I/@@@@ ( 1249): Calculating balance: 45000000
I/@@@@ ( 1249): Calculating balance: 0
D/QuizApp ( 1249): Setting text Balance is 0
D/QuizApp ( 1249): Balance text is 8 Available Balance: $0
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@45020d10
I/@@@@ ( 1249): Calculating balance: 55000000
I/@@@@ ( 1249): Calculating balance: 10000000
D/QuizApp ( 1249): Setting text Balance is 10,000,000
D/QuizApp ( 1249): Balance text is 9 Available Balance: $10,000,000
W/InputManagerService( 59): Window already focused, ignoring focus gain of: com.android.internal.view.IInputMethodClient$Stub$Proxy@44f85c58
I/@@@@ ( 1249): Calculating balance: 55000000
I/@@@@ ( 1249): Calculating balance: 20000000
D/QuizApp ( 1249): Setting text Balance is 20,000,000
D/QuizApp ( 1249): Balance text is 10 Available Balance: $20,000,000
I can only assume that I am getting some unprintable characters sometimes returned from the call to NumberFormat.getIntegerInstance().format
I am experiencing this issue on an emulator and 3 different phones running different OS's from 2.2 to 4.1.
So What alternatives do I have to format a number in such a way as it will reliably display in a TextView?
UPDATE The full fragment activity
public class NewTeamFragmentActivity extends SherlockFragmentActivity implements TabListener, OnBalanceChangedListener, OnTeamDataChangedListener{
static final int NUM_ITEMS = 3;
private ViewPager mPager;
private NewTeamSwipeAdapter mAdapter;
private TeamModel mTeam;
private TextView mTvBalance;
private int mi = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_new_team);
// Show the Up button in the action bar.
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setNavigationMode( ActionBar.NAVIGATION_MODE_TABS );
mAdapter = new NewTeamSwipeAdapter(getSupportFragmentManager());
mPager = (ViewPager)findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
mPager.setOnPageChangeListener(
new ViewPager.SimpleOnPageChangeListener() {
@Override
public void onPageSelected(int position) {
// When swiping between pages, select the
// corresponding tab.
getSupportActionBar().setSelectedNavigationItem(position);
}
});
setupTabs();
setupModels();
createFragments();
}
private void createFragments() {
NewTeamFragment teamFragment = new NewTeamFragment();
Bundle arguments = new Bundle();
FragmentTransaction trans = getSupportFragmentManager().beginTransaction();
trans.replace(R.id.new_team_fragment_container, teamFragment);
trans.commit();
}
private void setupModels() {
mTeam = new TeamModel();
}
private void setupTabs() {
add_tab("Car 1");
add_tab("Car 2");
add_tab("Submit Team");
}
private void add_tab(String tabText) {
Tab tab = getSupportActionBar().newTab();
tab.setText(tabText);
tab.setTabListener(this);
getSupportActionBar().addTab(tab);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getSupportMenuInflater().inflate(R.menu.activity_new_team, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
// This ID represents the Home or Up button. In the case of this
// activity, the Up button is shown. Use NavUtils to allow users
// to navigate up one level in the application structure. For
// more details, see the Navigation pattern on Android Design:
//
// http://developer.android.com/design/patterns/navigation.html#up-vs-back
//
NavUtils.navigateUpFromSameTask(this);
return true;
}
return super.onOptionsItemSelected(item);
}
public static class NewTeamSwipeAdapter extends FragmentPagerAdapter {
public NewTeamSwipeAdapter(FragmentManager fm) {
super(fm);
}
@Override
public int getCount() {
return NUM_ITEMS;
}
@Override
public Fragment getItem(int position) {
Fragment return_frag = null;
if (position < 2){
Bundle arguments = new Bundle();
arguments.putInt(KeyConstants.CAR_ID_KEY, position);
NewCarFragment fragment = new NewCarFragment();
fragment.setArguments(arguments);
return_frag = fragment;
} else if (position == 2) {
SubmitTeamFragment fragment = new SubmitTeamFragment();
return_frag = fragment;
}
return return_frag;
}
@Override
public CharSequence getPageTitle (int position){
CharSequence title = "Unknown!";
if (position < 2){
title = "Car " + (position + 1);
}else if(position == 2){
title = "Submit Team";
}
return title;
}
}
@Override
public void onTabSelected(Tab tab, FragmentTransaction ft) {
mPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
@Override
public void onTabReselected(Tab tab, FragmentTransaction ft) {
// TODO Auto-generated method stub
}
@Override
public void onBalanceChanged(int position, CarModel car) {
// Try to change the car but if it has not already been added then add it instead using outOfBounds
// exception to check if item exists
mi ++;
try {
mTeam.cars.set(position, car);
}catch(java.lang.IndexOutOfBoundsException e){
mTeam.cars.add(car);
}
if (mTvBalance == null){
mTvBalance = (TextView) findViewById(R.id.lblBalance);
}
String balance = mTeam.calcBalance();
Util.log_debug_message("Setting text Balance is " + balance);
String balance_text = mi + " Available Balance: $" + balance;
Util.log_debug_message("Balance text is " + balance_text);
mTvBalance.setText(balance_text);
}
@Override
public void onTeamDataChanged(TeamModel mTeam) {
// TODO Auto-generated method stub
if (this.mTeam == null){
this.mTeam = mTeam;
}else{
this.mTeam.setTeamName(mTeam.getTeamName());
}
}
}
UPDATE - This is definitely an issue with NumberFormat.getIntegerInstance()
Integer.toString(balance); causes no problems whatsoever. So how can I reliably format an integer with commas?
Upvotes: 0
Views: 593
Reputation: 12837
I am pretty certain that there is a bug in android.
Looking at the docs for NumberFormat.getIntegerInstance()
(http://developer.android.com/reference/java/text/NumberFormat.html) there is mention that this method should not be called too many times kinda wrapped up in a it's better for performance to get the integer instance once, set it up as a member variable and re-use it.
Quote
If you are formatting multiple numbers, it is more efficient to get the format and use it multiple times so that the system doesn't have to fetch the information about the local language and country conventions multiple times.
Well of course it is but performance issues like this are not really mentioned in other classes so I suspect that the Android team are aware that this method might prove to be a little unreliable if called too frequently but they aren't really going to do anything about it. I would probably agree that if this is the case then why should they? It's up to the developer to optimise code and if code is optimised then you won't get the problem.
The thing is that my code was not optimised at all. Because this method was being used in multiple spinners that were all being loaded at the same time.
I have refactored my code and made sure this method is called as few times as possible by assigning the result to a member variable and only ever calling it if the member variable is null and all seems good but I am left with an uneasy feeling that the functionality is not as stable as it could be and that this might come back to bite.
Upvotes: 1