0

A seemingly simple requirement - to update UI from an IntentService. In the shot below, when the Start Service button is clicked, I need to show a ProgressBar above the only TextView and after a delay, remove the ProgressBar.

AVD Screenshot

Found a lot of answers on SO, but somehow unable to still crack it. I understand from this that LocalBroadcastManager is a good way to go. I have also tried following this, (an approach with Handlers), but it fails to show the ProgressBar too. Finally, based on this answer, here's what I have ended up with. The output I've managed so far is just the Toasts appearing in succession, after all the logging is done.

Greatly appreciate if you could point out where I am going wrong, been struggling for quite some time now. Many thanks in advance!

MainActivity updated

public class MainActivity extends AppCompatActivity
{
    private static final String TAG = "MainActivity";
    private ProgressBar pb;
    private MyBroadcastReceiver myBroadcastReceiver;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        pb = (ProgressBar) findViewById(R.id.pb);
        myBroadcastReceiver = new MyBroadcastReceiver();

        LocalBroadcastManager.getInstance(this).registerReceiver(myBroadcastReceiver, new IntentFilter("ACTION"));
    }

    private void updateUI(boolean show)
    {
        if (show)
            pb.setVisibility(View.VISIBLE);
        else
            pb.setVisibility(View.GONE);
//        Toast.makeText(this, "UI Updated...", Toast.LENGTH_LONG).show();
    }

    public void startIt(View view)
    {
        Intent intent = new Intent(this, NanisIntentService.class);
        startService(intent);
    }

    public class MyBroadcastReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(final Context context, Intent intent)
        {
            String action = intent.getAction();
            Log.e(TAG, "In onReceive(): " + action);
            if (action.equals("ACTION"))
            {
                updateUI(true);
            } // of if (action = "ACTION")
            else if (action.equals("NOITCA"))
            {
                updateUI(false);
            } // of else of if (action = "ACTION")
        } // of onReceive()
    } // of class MyBroadcastReceiver
}

IntentService updated

public class NanisIntentService extends IntentService
{
    private static final String TAG = "NanisIntentService";

    public NanisIntentService()
    {
        super("NanisIntentService");
    }

    @Override
    protected void onHandleIntent(Intent intent)
    {
        Log.e(TAG, "In onHandleIntent(): Intent is being serviced");
        LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("ACTION"));
        int i = 0;
        while (i <= 50)
        {
            try
            {
                Thread.sleep(50);
                i++;
                Log.e("", "" + i);
            } catch (InterruptedException e)
            {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void onDestroy()
    {
        super.onDestroy();
        LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("NOITCA"));
        Log.e(TAG, "In onDestroy(): The service has been destroyed");
    }
}

    @Override
    public void onStart(Intent intent, int startId)
    {
        super.onStart(intent, startId);
        LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("ACTION"));
    }

    @Override
    public void onDestroy()
    {
        super.onDestroy();
        LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("NOITCA"));
        Log.e(TAG, "In onDestroy(): The service has been destroyed");
    }
}

    @Override
    public void onDestroy()
    {
        super.onDestroy();
        LocalBroadcastManager.getInstance(testing.com.myintentservice.NanisIntentService.this).sendBroadcast(new Intent().setAction("NOITCA"));
        Log.e(TAG, "In onDestroy(): The service has been destroyed");
    }
}

activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="testing.com.myintentservice.MainActivity">

    <ProgressBar
        android:id="@+id/pb"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_above="@+id/tv"
        android:layout_centerHorizontal="true"
        android:indeterminate="true"
        android:visibility="gone"/>

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerInParent="true"
        android:text="Hello IntentService!"
        android:textColor="#1298CE"
        android:textSize="32sp"/>

    <Button
        android:id="@+id/bt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:text="Start Service"
        android:onClick="startIt"/>
</RelativeLayout>

AndroidManifest

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service android:name="testing.com.myintentservice.NanisIntentService"/>
    </application>
Community
  • 1
  • 1
Narayana J
  • 307
  • 5
  • 17

2 Answers2

2

First, you are tying up the main application thread for ~2.5 seconds. This will freeze your UI during this period of time. Do not do this.

Second, you are calling updateUI() once before the ~2.5 seconds, and once after. Since you are tying up the main application thread during that time, this will have the same visual effect as calling updateUI() twice in succession after the delay. updateUI() toggles the ProgressBar visibility, so two calls will cancel each other out, leaving you in the same state as you started.

I need to show a ProgressBar above the only TextView and after a delay, remove the ProgressBar.

Showing a ProgressBar for 2.5 seconds, irrespective of any actual work being done, is rather arbitrary.

That being said, call updateUI() once, then use pb.postDelayed() to schedule a Runnable to run 2500 milliseconds later, where the run() method in the Runnable calls updateUI() the second time. This avoids you blocking the main application thread, so it allows the first updateUI() call to take effect, while still giving you the 2.5-second duration.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Thanks for the blazing fast response, @CommonsWare! Sorry, but I missed mentioning that this is an _experiment_, a _PoC_ - so instead of the delay of 2.5 seconds, there's going to be an indeterminate internet download, during which time I need to show the `ProgressBar`. Will your suggestion still hold good? Greatly appreciate if you could kindly elaborate the solution a bit more if possible. Cheers! – Narayana J Mar 17 '16 at 20:49
  • 1
    @NarayanaJ: "Will your suggestion still hold good? " -- assuming that the duration of the `ProgressBar` is based on the service, you may as well set things up that way to start with. Use two different `LocalBroadcastManager` events for "show" and "hide". In the short term, put the delay in the service, in the form of "fake work" that the service does. Then, you can replace the fake work with real work, and your `ProgressBar` logic does not have to change. – CommonsWare Mar 17 '16 at 20:53
  • please check the updated code in my original question. What I did is: -Moved the 'fake work' to the `IntentService` -Setup 2 actions for the broadcast, one to show and one to hide the `ProgressBar` Neither am I sure if the `Runnable` call (that you originally suggested) should still be made, nor _how exactly_ it should be made. The `ProgressBar` still doesn't show. Kindly reply. – Narayana J Mar 17 '16 at 23:28
  • @NarayanaJ: Your second `LocalBroadcastManager` message is not until `onDestroy()`, and I do not see where you are ever stopping the service. Also, you are once again tying up the main application thread, because you put all your logic in `onStart()` rather than `onHandleIntent()`. – CommonsWare Mar 17 '16 at 23:33
  • Had realized the mistake and your point perfectly corroborated it. Please see my updated `IntentService` in the original question. The `ProgressBar` does show up now, but doesn't stop ever. In [another answer by you](http://stackoverflow.com/questions/10250745/proper-way-to-stop-intentservice), there's _no need_ to stop an `IntentService`. So, how and where should I stop it? Thanks! – Narayana J Mar 17 '16 at 23:57
  • @NarayanaJ: I misspoke in my previous comment. The `IntentService` should shut down on its one, once `onHandleIntent()` returns. `onStart()` on a `Service` has been deprecated for about six years. Please get rid of it, moving your `LocalBroadcastManager` line into `onHandleIntent()`. Then, see if your `IntentService` shuts down properly. – CommonsWare Mar 18 '16 at 00:25
  • No problem, @CommonsWare. Please eyeball the updated `IntentService`. `onDestroy()` gets called, but the `ProgressBar` doesn't stop. (Strange however, that Studio gave no deprecation warnings for such an old method...) – Narayana J Mar 18 '16 at 00:45
0

Thanks so much, @CommonsWare! Finally got it to work. It needed two receivers for the two different actions, which I discovered here.

For the benefit of those looking for the final working code...

Result screens:- after clicking and after processing

Pardon any copy-paste ghosts in the code, it is after all a PoC, albeit functional.

Narayana J
  • 307
  • 5
  • 17
  • @user3705478, wonder how it vanished from pastie. Will update this post with the code when I get some free time. Till then, please bear. – Narayana J Sep 14 '16 at 03:36