Question

I'm using the built in action bar. I want to display in the action bar an animated loader while fetching data, and display a "refresh" icon when not. The simple way I found is to use 2 menu items and show one / hide the other and then the opposite. So here is my menu :

<item android:id="@+id/menuItemRefresh" 
      android:title="Refresh" 
      android:showAsAction="always" 
      android:icon="@drawable/ic_menu_refresh"
      android:visible="false" />
<item android:id="@+id/menuItemProgress"
      android:actionLayout="@layout/progress"
      android:showAsAction="always"
      android:visible="true" />

I'm using an actionLayout (as you can see) because I did not figure out how to create an indeterminate progressbar rotation in my Menu. Here is the corresponding actionLayout :

<?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"
    android:orientation="vertical"
    android:id="@+id/layoutProgress" >

    <ProgressBar
        android:id="@+id/menuItemRefresh"
        android:layout_width="30dp"
        android:layout_height="30dp"
        android:layout_marginLeft="13dp"
        android:layout_marginRight="13dp" />

</LinearLayout>

My activity :

public class MainActivity extends Activity implements OnItemClickListener
{
    Handler             handler;
    MenuItem            menuItemProgress;
    MenuItem            menuItemRefresh;

    int                 currentPage;
    List<CloudAppItem>  items;
    boolean             currentlyLoading    = false;
    ListView            listView;
    FilesAdapter        filesAdapter;
    LinearLayout        emptyView;

    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        Log.i("MainActivity", "Started");

        super.onCreate(savedInstanceState);

        setContentView(R.layout.main);
        setHandler();
        setListing();

        loadItems();
    }

    @Override
    protected void onResume()
    {
        super.onResume();

        if (isFirstRun() && haveCredentialsChanged())
        {
            setFirstRun(false);
        }

        if (isFirstRun())
        {
            firstRun();
        }
        else if (haveCredentialsChanged())
        {
            setCredentialsChanged(false);
            loadFiles(true, 0);
        }
        else
        {
            loadFiles(false, 0);
        }
    }

    @Override
    public Object onRetainNonConfigurationInstance()
    {
        return items;
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu)
    {               
        MenuInflater inflater = getMenuInflater();
        inflater.inflate(R.menu.menu, menu);

        this.menuItemProgress = menu.findItem(R.id.menuItemProgress);
        this.menuItemRefresh = menu.findItem(R.id.menuItemRefresh);

        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item)
    {
        switch (item.getItemId())
        {
            case R.id.menuItemPreferences:

                startActivity(new Intent(MainActivity.this, PreferencesActivity.class));

            break;

            case R.id.menuItemRefresh:

                loadFiles(true, 0);

            break;
        }

        return false;
    }

    private boolean isFirstRun()
    {
        return Prefs.getPreferences(this).getBoolean(Prefs.FIRST_RUN, true);
    }

    private void firstRun()
    {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage("First run");
        builder.setCancelable(false);
        builder.setPositiveButton("ok", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which)
            {
                startActivity(new Intent(MainActivity.this, PreferencesActivity.class));
            }
        });

        AlertDialog alert = builder.create();
        alert.show();
    }

    private void setFirstRun(boolean b)
    {
        Prefs.getPreferences(this).edit().putBoolean(Prefs.FIRST_RUN, b).commit();
    }

    private boolean haveCredentialsChanged()
    {
        return Prefs.getPreferences(this).getBoolean(Prefs.CREDENTIALS_CHANGED, false);
    }

    private void setCredentialsChanged(boolean b)
    {
        Prefs.getPreferences(this).edit().putBoolean(Prefs.CREDENTIALS_CHANGED, b).commit();
    }

    private void setListing()
    {
        listView = (ListView) findViewById(R.id.listView);
        emptyView = (LinearLayout) findViewById(R.id.emptyView);
        listView.setOnItemClickListener(this);
        listView.setEmptyView(emptyView);
    }

    @SuppressWarnings("unchecked")
    private void loadItems()
    {
        Object obj = getLastNonConfigurationInstance();
        if (obj != null)
        {
            items = (List<CloudAppItem>) obj;
            MainActivity.this.handler.sendEmptyMessage(0);
        }
        else
        {
            if (!isFirstRun())
                loadFiles(true, 0);
        }
    }

    private void showProgressIcon(boolean show)
    {
        if(this.menuItemProgress != null && this.menuItemRefresh != null)
        {                       
            if(show)
            {               
                this.menuItemProgress.setVisible(true); 
                this.menuItemRefresh.setVisible(false);                         
            }
            else
            {
                this.menuItemRefresh.setVisible(true);

                // PROBLEM : LINE TRIGGERING ERROR
                this.menuItemProgress.setVisible(false);    
            }
        }
    }

    @Override
    public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3)
    {
        if(this.items.get(arg2).isFake())
        {
            filesAdapter.lastItemLoading = true;
            filesAdapter.notifyDataSetChanged();

            int page = (int)(this.items.size()/Integer.valueOf(Prefs.getPreferences(this).getString(Prefs.FILES_PER_REQUEST, "20")))+1;     
            loadFiles(false, page);
        }
        else
        {
            startActivity(new Intent(this, FileInfosActivity.class));
        }               
    }

    /**
     * @param reload : clean the list before loading new items
     * @param page : if 0, items are prepended, else, items are appended
     */
    private void loadFiles(final boolean reload, final int page)
    {               
        if (!currentlyLoading)
        {           
            currentlyLoading = true;
            showProgressIcon(true);

            new Thread(new Runnable() {
                @Override
                public void run()
                {
                    // REQUESTING WEB SERVICE HERE

                    MainActivity.this.handler.sendEmptyMessage(0);                      
                }
            }).start();
        }
    }

    private void setHandler()
    {
        this.handler = new Handler() {

            @Override
            public void handleMessage(Message msg)
            {
                currentlyLoading = false;                               

                if (filesAdapter == null)
                {
                    filesAdapter = new FilesAdapter(MainActivity.this, MainActivity.this.items);
                    listView.setAdapter(filesAdapter);
                }
                else
                {   
                    filesAdapter.refill(MainActivity.this.items);
                }

                // PROBLEM : LINE TRIGGERING ERROR
                showProgressIcon(false);
            }
        };
    }
}

Everything is fine and working when I run the application. The problem appears when I rotate the phone.

The stacktrace :

FATAL EXCEPTION: main
java.lang.RuntimeException: Unable to start activity
ComponentInfo{com.quanturium.androcloud/com.quanturium.androcloud.MainActivity}:
java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to
android.widget.ProgressBar$SavedState
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1955)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:1980)
at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3347)
at android.app.ActivityThread.access$700(ActivityThread.java:122)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1150)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:4340)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:784)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:551)
at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.ClassCastException: android.view.AbsSavedState$1 cannot be cast to
android.widget.ProgressBar$SavedState
at android.widget.ProgressBar.onRestoreInstanceState(ProgressBar.java:1093)
at android.view.View.dispatchRestoreInstanceState(View.java:9876)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2330)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2330)
at android.view.ViewGroup.dispatchRestoreInstanceState(ViewGroup.java:2330)
at android.view.View.restoreHierarchyState(View.java:9854)
at com.android.internal.policy.impl.PhoneWindow.restoreHierarchyState(PhoneWindow.java:1625)
at android.app.Activity.onRestoreInstanceState(Activity.java:906)
at android.app.Activity.performRestoreInstanceState(Activity.java:878)
at android.app.Instrumentation.callActivityOnRestoreInstanceState(Instrumentation.java:1100)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:1933)
... 12 more

The actionLayout (@+id/menuItemProgress) seems to cause the problem. Everything is fine with the refresh item (@+id/menuItemRefresh). I wrote as a comment where the problem is. When I comment OUT the line "PROBLEM : " the rotation works well.

Was it helpful?

Solution

Android doesn't seem to be able to restore your view's saved state.

This happens because you have two views with the same ID. Since they are not of the same class, the cast fails and crashes.

<item android:id="@+id/menuItemRefresh" 

and

<ProgressBar
       android:id="@+id/menuItemRefresh"

This is your problem.

OTHER TIPS

I had the same problem and solved it by adding the following to my activity declaration in AndroidManifest.xml:

android:configChanges="orientation|screenSize|keyboardHidden"

This page led me to the solution. Hope this helps.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top