0

I need to pass the activity context to my service as soon as the activity is being constructed. Here is my code:

public class myService extends Service
{
    private AppCompatActivity activity;

    public void setActivity(AppCompatActivity activity)
    {
        this.activity = activity;
    }
}

public class myActivity extends AppCompatActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        // ... some things are being executed and myService is being bound
        mService.setActivity(this);
    }
}

I get NullPointerException as - I suppose - the myActivity class is still being constructed and the reference cannot be passed. How can I make Android run this method after onCreate? I found some solutions for Java involving factory pattern but I'm not sure how can I use it, if I can use it in my case at all.

Skorpion
  • 21
  • 1
  • 9
  • what is mService, is it intantiated – sdfbhg Oct 25 '17 at 18:49
  • also we dont use passing of context to do work on the ui thread we use broadcast Intents – sdfbhg Oct 25 '17 at 18:51
  • Is it a big issue when we do it the other way? The whole application is already wrote this way and I'm not sure I would be able to force such change all by myself. – Skorpion Oct 25 '17 at 18:59
  • sorry no its not, its just the way I learned from a book. You can do it however you like. – sdfbhg Oct 25 '17 at 19:00

2 Answers2

1

Service is a Context by itself. So if you need the Context only, you can just call this in your Serviceclass.

Alternatively, you should pass the Activity to the service before starting it. Make sure you pass the Activity after calling super.onCreate(bundle);

However, you should not manipulate your Activity or it's views from a Service. A better way is notifying your Activity from your Service.

Notify activity from service

Edit: Observer pattern

Create a new class called NotificationCenter.java

public class NotificationCenter {

    private static int totalEvents = 1;

    public static final int updateActivity = totalEvents++;
    // you can add more events
    // public static final int anotherEvent = totalEvents++;

    private final SparseArray<ArrayList<Object>> observers = new SparseArray<>();
    private final SparseArray<ArrayList<Object>> removeAfterBroadcast = new SparseArray<>();
    private final SparseArray<ArrayList<Object>> addAfterBroadcast = new SparseArray<>();

    private int broadcasting = 0;

    public interface NotificationCenterDelegate {
        void didReceivedNotification(int id, Object... args);
    }

    private static volatile NotificationCenter Instance = null;

    public static NotificationCenter getInstance() {
        NotificationCenter localInstance = Instance;
        if (localInstance == null) {
            synchronized (NotificationCenter.class) {
                localInstance = Instance;
                if (localInstance == null) {
                    Instance = localInstance = new NotificationCenter();
                }
            }
        }
        return localInstance;
    }

    public void postNotificationName(int id, Object... args) {
        broadcasting++;
        ArrayList<Object> objects = observers.get(id);
        if (objects != null && !objects.isEmpty()) {
            for (int a = 0; a < objects.size(); a++) {
                Object obj = objects.get(a);
                ((NotificationCenterDelegate) obj).didReceivedNotification(id, args);
            }
        }
        broadcasting--;
        if (broadcasting == 0) {
            if (removeAfterBroadcast.size() != 0) {
                for (int a = 0; a < removeAfterBroadcast.size(); a++) {
                    int key = removeAfterBroadcast.keyAt(a);
                    ArrayList<Object> arrayList = removeAfterBroadcast.get(key);
                    for (int b = 0; b < arrayList.size(); b++) {
                        removeObserver(arrayList.get(b), key);
                    }
                }
                removeAfterBroadcast.clear();
            }
            if (addAfterBroadcast.size() != 0) {
                for (int a = 0; a < addAfterBroadcast.size(); a++) {
                    int key = addAfterBroadcast.keyAt(a);
                    ArrayList<Object> arrayList = addAfterBroadcast.get(key);
                    for (int b = 0; b < arrayList.size(); b++) {
                        addObserver(arrayList.get(b), key);
                    }
                }
                addAfterBroadcast.clear();
            }
        }
    }

    public void addObserver(Object observer, int id) {
        if (broadcasting != 0) {
            ArrayList<Object> arrayList = addAfterBroadcast.get(id);
            if (arrayList == null) {
                arrayList = new ArrayList<>();
                addAfterBroadcast.put(id, arrayList);
            }
            arrayList.add(observer);
            return;
        }
        ArrayList<Object> objects = observers.get(id);
        if (objects == null) {
            observers.put(id, (objects = new ArrayList<>()));
        }
        if (objects.contains(observer)) {
            return;
        }
        objects.add(observer);
    }

    public void removeObserver(Object observer, int id) {
        if (broadcasting != 0) {
            ArrayList<Object> arrayList = removeAfterBroadcast.get(id);
            if (arrayList == null) {
                arrayList = new ArrayList<>();
                removeAfterBroadcast.put(id, arrayList);
            }
            arrayList.add(observer);
            return;
        }
        ArrayList<Object> objects = observers.get(id);
        if (objects != null) {
            objects.remove(observer);
        }
    }
}

Then make your Activities look like this, you receive messages from the Service in didReceivedNotification()

public class YourActivity implements NotificationCenter.NotificationCenterDelegate {

    @Override
    public void onPause() {
        NotificationCenter.getInstance().removeObserver(this, NotificationCenter.updateActivity);
        super.onPause();
    }

    @Override
    public void onResume() {
        NotificationCenter.getInstance().addObserver(this, NotificationCenter.updateActivity);
        super.onResume();
    }

    @Override
    public void didReceivedNotification(int id, Object... args) {
        if (id == NotificationCenter.updateActivity) {
            // do something with your activity, your service called this
        }
    }
}

Finally send messages in your Service to all the Activities which are listening:

NotificationCenter.getInstance().postNotificationName(NotificationCenter.updateActivity, optionalData);

Which is very nice, you don't have to pass Activity instances.

NotificationCenter source is from Telegram.

gi097
  • 7,313
  • 3
  • 27
  • 49
  • No, I need specifically context of current activities. As user changes pages in the app the service's reaction should change. Does it mean I can set a starting activity and then before every intent.startActivity() just call the setActivity() method? – Skorpion Oct 25 '17 at 18:56
  • Yes, make sure that your `Service` has the inited `Activity` and then actually run the `Service`. – gi097 Oct 25 '17 at 18:56
  • Oh, you mean I should pass the context before starting the Service, not the Activity? So the first step is to set the activity, next start the service, and when activity changes another context should be passed and the service restarted? Anyway I'll take a look at the notification approach. – Skorpion Oct 25 '17 at 19:03
  • Exactly! You should do that to prevent the `NullpointerException` – gi097 Oct 25 '17 at 19:05
  • So I definitly need to give a try to the notifications. I have one service that is being started as soon as the app pops up and runs all the time in background. It basically keeps TCP connection opened and should process incoming packets and update activities, that's why I needed the context. – Skorpion Oct 25 '17 at 19:16
  • Sorry for two comments in a row but is it what I'm looking for? https://developer.android.com/guide/components/broadcasts.html – Skorpion Oct 25 '17 at 19:18
  • Allright, you can also just set the new Activity without restarting the service. I also have some nice observer code for you which might be even better to update Activities. – gi097 Oct 25 '17 at 19:19
  • Great! As I don't now really much about Notifications yet it's all the same for me to work with them or with your observer code. Where could I find it? On what conditions could I use it? – Skorpion Oct 25 '17 at 19:25
  • Looks great. Can it work the other way around too? I mean notify the Service from Activity? – Skorpion Oct 25 '17 at 19:49
  • Yes, off course. Implement the delegate, and put addObserver in your service, after that call postNotificationName in Activity. – gi097 Oct 25 '17 at 19:52
  • So I get a little problem with your code. I can't really receive anything in Service. Service class doesn't have any onPause() and onResume() method so I wasn't able to override them. Instead I added addBoserver() method to class constructor but this doesn't work as well. The method didReceiveNotification() isn't called even once. – Skorpion Oct 25 '17 at 21:14
  • It should work. You can email me and we can see what we can do okay? – gi097 Oct 26 '17 at 05:46
0
public class myService extends Service
{
 public static myActivity activity;

 public static void setActivity(myActivity activity)
 {
     this.activity = activity;
 }
 public void useActivityExample()
{
  myService.myActivity.update();
 }
}

public class myActivity extends AppCompatActivity
{
    @Override
    protected void onCreate(Bundle savedInstanceState)
   {
       // ... some things are being executed and myService is being bound
      mService.setActivity(getActivity());
   }
}
sdfbhg
  • 109
  • 5
  • I can't use myActivity as the activity type in myService as I want to use multiple custom Activities and they would not fit. Also getActivity() cannot be resolved. – Skorpion Oct 25 '17 at 19:14
  • sorry it is not get Activity it is actually getContext, or myActivity.this – sdfbhg Oct 25 '17 at 19:17