Can't Update Notifications text after Forerground service init; Context issue
Asked Answered
B

2

8

I have a simple NanoHTTPD server running as a foreground service.

I am facing issues in updating the notifications with new content when a new request to a server comes in.

The foreground service starts up and the notification shows. No issue there. But can't update them later.

File structure
- Mainactivity
- NanoServer (server implementaion)
- NanoService (foreground service class)
- NotificationProvider (separate class to handle notifications)

NanoServer.java

    public Context context = getContext();
    public NotificationProvider notificationProvider;

    public NanoServer(int port) {
        super(8089);
    }

    @Override
    public Response serve(String uri, Method method,
                          Map<String, String> header, Map<String, String> parameters,
                          Map<String, String> files) {
        String answer = "";
        String msg;

        // doesnt work with the context. something wrong here I guess????
        notificationProvider = new NotificationProvider();
        notificationProvider.setNotification(context, "Title", uri, 0);

        FileInputStream fis = null;

        try {
            fis = new FileInputStream(uri);
            Log.w(TAG, uri + " found");
        } catch (FileNotFoundException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        return newChunkedResponse(Status.OK, "audio/mpeg", fis);

    }

    public Context getContext() {
        return context;
    }

NanoService.java

    String TAG = "NANOSERVICE";
    public Context context = this;
    public Handler handler = null;
    public static Runnable runnable = null;
    PowerManager powerManager;
    PowerManager.WakeLock wakeLock;
    WifiManager.WifiLock wifiLock;

    private NanoServer nanoServer;
    public NotificationProvider notificationProvider;

    public NanoService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        // TODO: Return the communication channel to the service.
        throw new UnsupportedOperationException("Not yet implemented");
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        // Start the httpd.
        try {
            nanoServer = new NanoServer(8089);
            nanoServer.start();
            Log.d(TAG, "Service with server started");
        } catch (IOException e) {
            e.printStackTrace();
            Toast.makeText(this, "Service failed to start.", Toast.LENGTH_LONG).show();
        }

        // Keep the CPU awake (but not the screen).
        powerManager = (PowerManager)getSystemService(Context.POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        wakeLock.acquire();

        // Keep the WIFI turned on.
        WifiManager wm = (WifiManager)context.getSystemService(Context.WIFI_SERVICE);
        wifiLock = wm.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, TAG);
        wifiLock.acquire();

        notificationProvider = new NotificationProvider();
        notificationProvider.setNotification(this, "Title", "Message", 0);

        // had to extend notificationprovider with notification
        startForeground(1, notificationProvider);
        Log.d(TAG, "Foreground service running");

        return Service.START_STICKY;

    }

    @Override
    public void onDestroy() {
        stopForeground(true);
        wakeLock.release();
        wifiLock.release();
        nanoServer.stop();
    }

NotificationProvider.java

public class NotificationProvider extends Notification {
    String TAG = "NOTIFICATIONPROVIDER";
    public NotificationProvider() {
    }

    public void setNotification(Context context, String notificationTitle, String notificationMessage, int notificationRequestCode){
        NotificationCompat.Builder builder =
                new NotificationCompat.Builder(context)
                        .setSmallIcon(R.drawable.ic_launcher_background)
                        .setContentTitle(notificationTitle)
                        .setContentText(notificationMessage)
                        .setTicker("My service")
                        .setColor(101)
                        .setWhen(System.currentTimeMillis())
                        .setOngoing(true)
                        .setPriority(NotificationCompat.PRIORITY_MAX);

        Intent intent = new Intent(context, MainActivity.class);
        PendingIntent contentIntent = PendingIntent.getActivity(context, notificationRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
        builder.setContentIntent(contentIntent);

        NotificationManager manager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
        manager.notify(0, builder.build());
        Log.d(TAG, "Got new Notification");
    }
}
Bolick answered 18/8, 2018 at 22:39 Comment(0)
C
0

I think the easiest solution will be using the same builder method for updating the notification.

Use this updated NotificationProvider.

Change new NotificationProvider() to NotificationProvider.getInstance() in NanoService (or anywhere else).

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.NotificationCompat;
import android.util.Log;

import com.iroid.videoeditor.R;
import com.iroid.videoeditor.main.MainActivity;

public class NotificationProvider extends Notification {

    private static NotificationProvider sInstance;

    private NotificationCompat.Builder mBuilder;
    private String TAG = "NOTIFICATIONPROVIDER";

    public static NotificationProvider getInstance() {
        if (sInstance == null)
            sInstance = new NotificationProvider();

        return sInstance;
    }

    // Prevent creating new instances from outside
    private NotificationProvider() {
    }

    public void setNotification(Context context, String notificationTitle, String
            notificationMessage, int notificationRequestCode) {

        NotificationManager manager = (NotificationManager) context.getSystemService(Context
                .NOTIFICATION_SERVICE);

        if (mBuilder == null) {
            // Notification doesn't exists. Need to create one.
            mBuilder =
                    new NotificationCompat.Builder(context)
                            .setSmallIcon(R.drawable.ic_launcher_background)
                            .setContentTitle(notificationTitle)
                            .setContentText(notificationMessage)
                            .setTicker("My service")
                            .setColor(101)
                            .setWhen(System.currentTimeMillis())
                            .setOngoing(true)
                            .setPriority(NotificationCompat.PRIORITY_MAX);

            Intent intent = new Intent(context, MainActivity.class);
            PendingIntent contentIntent = PendingIntent.getActivity(context,
                    notificationRequestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
            mBuilder.setContentIntent(contentIntent);

            manager.notify(0, mBuilder.build());
            Log.d(TAG, "Got new Notification");
        } else {
            // Notification exists. Simply update
        }
    }

}
Cheesy answered 21/8, 2018 at 13:54 Comment(3)
I stil need to provide the context in setNotification. That created problems in the non-activity class NanoServer. So will this still work? Is the way I have defined the context in NanoServer correct?Bolick
Sorry, I am not sure about it. But I think it will be okay and I use application context (getApplicationContext()) normally in similar situations.Cheesy
Alright I am having trouble with that. In the nonactivety class Nanoserver, can you show what you did?Bolick
R
0

The problem is most likely to be with your Notification ID, which must be unique within your application if you later want to update it.

manager.notify(0, builder.build());

Change it to a non-zero constant

private static final int NOTE_ID = 2794; //chosen arbitrarily

manager.notify(NOTE_ID, builder.build());

However you should also not hold (leak) a context, instead get the app context (or service context) when you use it.

To be able to get an Application Context at any time, implement an application class (and register it in the manifest)

public class MyApplication extends Application {

    public static Context appContext() {
        return this;
    }

}

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="...">

    <!-- The name here should match whatever you called your application class -->
    <application android:name=".MyApplication"
    ...>
        ...
    </application>

</manifest>
Rademacher answered 27/8, 2018 at 8:44 Comment(7)
I am not sure if this is proper, safe, wont leak memory, or efficient. but I did manage to solve it by passing the context to the Nanoserver.java from the service class, where the server is initialized. That way I have access to context within.Bolick
Ok, I am sure - It is creating a leak (and also potential crashes). A better option is to use the application class singleton so you can do MyApp.getAppContext(). Context is a tricky objectRademacher
Can you provide a sample? getAppContext() inside non-activity class wont work. I have checked various SO how to do this. No luck. It was then I figured why not pass the service context itself as a parameter while starting the server?Bolick
Well there is no app crashes I have noticed. But only thing which doesn't work is the foreground service itself. I do get 30-40 min activity but after that the phone kills it. For some reason it is not honoring the wakelock, I can see this - PowerManager: WakeLock finalized while still heldBolick
Take a look at the extra code I've added, that's how you make an application singletonRademacher
Do I have to modify the default application, MainActivity which you get when you create the app in the studio for the first time? Or I can just drop those lines in the manifest without anything inside?Bolick
Hey, it's a completely separate class you can create next to MainActivity (in my example, called MyApplication)Rademacher

© 2022 - 2024 — McMap. All rights reserved.