I need write a service which will update the list in MainActivity every 30sec. I use MVVM with ViewModel and LiveData and so my Service class looks like this:
public class ArticleJobService extends JobService {
public static String TAG = "ArticleJobService";
private Context context = this;
#Override
public boolean onStartJob(JobParameters jobParameters) {
Log.d(TAG, "onStartJob");
MainActivity.PAGE_NUMBER++;
LiveData<List<Article>> liveArticles = ArticleRepository.getInstance(getApplication()).getArticles(MainActivity.PAGE_NUMBER);
liveArticles.observeForever(new Observer<List<Article>>() {
#Override
public void onChanged(#Nullable List<Article> articles) {
Log.d(TAG, "onStartJob - onChanged!!!!!!");
liveArticles.removeObserver(this);
NotificationUtils.showNotification(context, articles.get(0).getSectionName(), articles.get(0).getWebTitle());
jobFinished(jobParameters, true);
}
});
return true;
}
}
Class for my notification:
public static void showNotification(Context context, String section, String title) {
PendingIntent contentPendingIntent = PendingIntent.getActivity
(context, REQUEST_CODE, new Intent(context, MainActivity.class),
PendingIntent.FLAG_UPDATE_CURRENT);
NotificationManager manager =
(NotificationManager) context.getSystemService(NOTIFICATION_SERVICE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
manager.createNotificationChannel(createNotificationChannel(context));
}
NotificationCompat.Builder builder = new NotificationCompat.Builder(context, CHANNEL_ID)
.setContentTitle(section)
.setContentText(title)
.setContentIntent(contentPendingIntent)
.setSmallIcon(R.drawable.app_icon)
.setPriority(NotificationCompat.PRIORITY_HIGH)
.setDefaults(NotificationCompat.DEFAULT_ALL)
.setAutoCancel(true);
manager.notify(0, builder.build());
}
When Onchanged in JobService works I get the list and show a notification. Notification opens MainActivity which makes new call to api as it always did. What changes do I have to make in order the MainActivity to show the list that I got from the service??? I really can't tie this up together.
I heard of IPC but wouldn't do that, I want some simpler practice which I sure exists which I just don't know about.
Also, there are two cases: Notification came and MainActivity is open, app is open but MainActivity is not in the foreground and app is on the background or closed. How should I handle each of these cases?
See also piece of code from MainActivity onCreate:
mArticleViewModel = ViewModelProviders.of(this).get(ArticleViewModel.class);
mArticleViewModel.getArticleList(PAGE_NUMBER).observe(this, articles -> {
Log.d(TAG, "List<Result> onChanged!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
mProgressBar.setVisibility(View.GONE);
mProgressBarMain.setVisibility(View.GONE);
mIsLoading = false;
mArticles = articles;
Please provide the best practices for this task, I know it's very common I just do it first time and using LiveData makes it way more complicated.
Here is Also Repository code:
public static ArticleRepository getInstance(Application application){
if(INSTANCE == null){
return new ArticleRepository(application);
}
return INSTANCE;
}
private ArticleRepository(Application application) {
Log.d(TAG, "ArticleRepository constructor");
mContext = application;
mArticles = new MutableLiveData<>();
ArticleRoomDatabase db = ArticleRoomDatabase.getInstance(application);
mArticleDao = db.articleDao();
}
public LiveData<List<Article>> getArticles(int page) {
Log.d(TAG, "getArticles");
if (NetworkUtils.isOnline(mContext)) {
Log.d(TAG, "isOnline");
mArticles = loadFromNetwork(page);
} else {
Log.d(TAG, "is NOT Online");
mArticles = loadFromDB(page);
}
}
You have this problem specifically because your Repository implementation is incorrect.
public LiveData<List<Article>> getArticles(int page) {
Log.d(TAG, "getArticles");
if (NetworkUtils.isOnline(mContext)) {
Log.d(TAG, "isOnline");
mArticles = loadFromNetwork(page);
} else {
Log.d(TAG, "is NOT Online");
mArticles = loadFromDB(page);
}
}
If you check the code for NetworkBoundResource, the trick is that you have a single LiveData that binds together the ability to both load from network, and to load from database.
In your case, you are replacing the database's auto-updating query results whenever you have network access - which is why you can't update the MainActivity.
The easiest way (without using a MediatorLiveData) is to have two separate functions on Repository: one for fetchFromNetwork, and one for fetchFromDatabase. The MainActivity should always fetch from database, while the Service always triggers load from network (and inserts it directly into database via a Dao).
This way, the observe function in MainActivity will receive the latest data when Service inserts the data into DB on background thread.
Related
First time using Stackoverflow !
I have an issue with my Workmanager and I'm asking for help:
When I run my app, its executing without switching my switch to ON. It's happening every time when I install (run) my app, my notification appears without doing anything. (it still works when I'm using my switch after launching )
MyWorker.java
public class MyWorker extends Worker {
private Workmate workmate;
private String messageBody;
public MyWorker(#NonNull Context context, #NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
#NonNull
#Override
public Result doWork() {
retrievesWorkmateData();
return Result.success();
}
SettingsActivity.java
#Override
public int getLayout() {
return R.layout.activity_settings;
}
#Override
protected void onConfigureDesign() {
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(this);
configureToolbar();
spinnerLanguage();
final SharedPreferences.Editor editor = mSharedPreferences.edit();
boolean notificationBoolean = mSharedPreferences.getBoolean(BOOLEAN, false);
final OneTimeWorkRequest simpleRequest = new OneTimeWorkRequest.Builder(MyWorker.class)
.build();
UUID workId = simpleRequest.getId();
if (notificationBoolean) {
mSwitch.setChecked(true);
}
mSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (mSwitch.isChecked()) {
editor.putBoolean(BOOLEAN, true);
WorkManager.getInstance().enqueue(simpleRequest);
Toast.makeText(this, getResources().getString(R.string.Alarm_manager_start), Toast.LENGTH_SHORT).show();
} else {
editor.putBoolean(BOOLEAN, false);
WorkManager.getInstance().cancelWorkById(workId);
Toast.makeText(this, getResources().getString(R.string.Alarm_manager_cancel), Toast.LENGTH_SHORT).show();
}
editor.apply();
});
}
Have a nice day.
inside onConfigureDesign you are creating OneTimeWorkRequest an obtain its UUID workId = simpleRequest.getId(). this id is used inside OnCheckedChangeListener for starting or canceling work for WorkManager. consider this scenario: app doesn't have any pending work to do and user navigates to this switch and schedule work with given UUID. then user quits app, even kill it, and then got back to app and navigates again to this switch. onConfigureDesign is called again, new OneTimeWorkRequest is created and it have new UUID. this id won't cancel your previously set request, because it had/have another id... still switch is checked, as its checked/unchecked state is basing just on some boolean in SharedPreferences
solution would be to store in SharedPreferences this UUID (String in fact, use toString() and fromString(str)) when work is scheduled and remove it from there when canceling or doWork() gets called. initial state of switch should be also set basing on presence of this (any) id in shared prefs
welcome on SO :)
while making an Android app, I have ran in the following problem:
My MainActivity looks like this:
...
private String token;
#Override
protected void onCreate(Bundle savedInstanceState) {
...
tokenSetter();
}
private void tokenSetter() {
FirebaseInstanceId.getInstance().getInstanceId()
.addOnCompleteListener(task -> {
if (!task.isSuccessful()) {
Log.w("TRAZENITOKEN", "getInstanceId failed", task.getException());
return;
}
// Get new Instance ID token
String token = Objects.requireNonNull(task.getResult()).getToken();
setToken(token);
Log.d("TRAZENITOKEN", "onGetToken: " + token);
// Log and toast
// Log.d("TRAZENITOKEN", "onComplete: " + token);
});
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
I know that the token value is being set as in an another method inside this MainActivity class, when I call getToken(), I get the value.
However, when I try to call getToken from an another Activity, something like this:
...
button.setOnClickListener(view -> {
FirebaseActions firebaseActions = new FirebaseActions();
MainActivity mainActivity = new MainActivity();
//firebaseActions.setUserNameOnToken(editText.getText().toString());
if(mainActivity.getToken() != null) editText.setText(mainActivity.getToken());
else editText.setText("Skafiskafnjak");
});
(I opted for the editText.setText method for debugging purposes, I am going to use it in the commented way)
The code snippet above always goes to the else part as the getToken is null.
Why does it return null in this class, if it returns a value in it's own class?
Could it be perhaps because I did
MainActivity mainActivity = new MainActivity();
An answer would be appreciated.
Thanks in advance!
MainActivity mainActivity = new MainActivity();
This activity instance is not the same one that the Android system created where you see the token being set. Besides, we never create an activity with new. The Android system creates activities according to the activity lifecycle and your code must work within this structure. To pass data between activities, you need to send it in the Intent when you call startActivity(). See the documentation for an example of how to do this.
Creating new activity causes new instance of that activity, therefor a new token which would be null
Send the token through the intent as String data from your MainActivity, and then from the second Activity, grab that String data(token), and do with it whatever you want.
Intent intent = new Intent(MainActivity.this, SecondActivity.class);
intent.putExtra("token", yourTokenValue);
startActivity(intent);
and in onCreate method of your SecondActivity,you can use getIntent().getStringExtra("token"); to retrieve the token value which was passed from the first Activity
String token= getIntent().getStringExtra("token");
Hi I make Android application for Xamarin. I have created a simple application in the Android studio. so any answer welcome either Java or C#
I have a service(GPS service) and 2 Activities.
MainActivity - GPS service are well connected with the broadcast. I hope MainActivity -> Another activity real time GPS point.(It is also okay to send from the GPS service to another activity.) but it is fail...app is dead..
MainActivity code
private void RegisterService()
{
_gpsServiceConnection = new GPSServiceConnection(_binder);
_gpsServiceIntent = new Intent(Android.App.Application.Context, typeof(GPS.GPSService));
BindService(_gpsServiceIntent, _gpsServiceConnection, Bind.AutoCreate);
}
private void RegisterBroadcastReceiver()
{
IntentFilter filter = new IntentFilter(GPSServiceReciever.LOCATION_UPDATED);
filter.AddCategory(Intent.CategoryDefault);
_receiver = new GPSServiceReciever();
RegisterReceiver(_receiver, filter);
}
private void UnRegisterBroadcastReceiver()
{
UnregisterReceiver(_receiver);
}
public void UpdateUI(Intent intent)
{
LatLng_txt.Text = intent.GetStringExtra("Location");
Lat = intent.GetDoubleExtra("Lat", 0.0);
Lng = intent.GetDoubleExtra("Lng", 0.0);
}
protected override void OnResume()
{
base.OnResume();
RegisterBroadcastReceiver();
}
protected override void OnPause()
{
base.OnPause();
UnRegisterBroadcastReceiver();
}
[BroadcastReceiver]
internal class GPSServiceReciever : BroadcastReceiver
{
public static readonly string LOCATION_UPDATED = "LOCATION_UPDATED";
public override void OnReceive(Context context, Intent intent)
{
if (intent.Action.Equals(LOCATION_UPDATED))
{
Instance.UpdateUI(intent);
}
}
}
GPS Service code
public void OnLocationChanged(Location location)
{
try
{
_currentLocation = location;
if (_currentLocation == null)
{
_location = "Unable to determine your location.";
}
else
{
_location = String.Format("{0}, {1}", _currentLocation.Latitude, _currentLocation.Longitude);
Geocoder geocoder = new Geocoder(this);
IList<Address> addressList = geocoder.GetFromLocation(_currentLocation.Latitude,
_currentLocation.Longitude, 10);
Address addressCurrent = addressList.FirstOrDefault();
if (addressCurrent != null)
{
StringBuilder deviceAddress = new StringBuilder();
for (int i = 0; i < addressCurrent.MaxAddressLineIndex; i++)
{
deviceAddress.Append(addressCurrent.GetAddressLine(i)).AppendLine(",");
}
_address = deviceAddress.ToString();
}
else
{
_address = "Unable to determine the address.";
}
IList<Address> source = geocoder.GetFromLocationName(_sourceAddress, 1);
Address addressOrigin = source.FirstOrDefault();
var coord1 = new LatLng(addressOrigin.Latitude, addressOrigin.Longitude);
var coord2 = new LatLng(addressCurrent.Latitude, addressCurrent.Longitude);
var distanceInRadius = Utils.HaversineDistance(coord1, coord2, Utils.DistanceUnit.Miles);
_remarks = string.Format("Your are {0} miles away from your original location.", distanceInRadius);
Intent intent = new Intent(this, typeof(MainActivity.GPSServiceReciever));
intent.SetAction(MainActivity.GPSServiceReciever.LOCATION_UPDATED);
intent.AddCategory(Intent.CategoryDefault);
intent.PutExtra("Location", _location);
intent.PutExtra("Lat", _currentLocation.Latitude);
intent.PutExtra("Lng", _currentLocation.Longitude);
SendBroadcast(intent);
}
}
catch
{
_address = "Unable to determine the address.";
}
}
Is not there a good way?
I understood your problem.But dont know more about GPS etc.I have faced the same problem when I was creating Music App.
Two activities were there and one service.And successfully got real time song position and song data from both activities.
My MainActivity has
ServiceConnection sc=null;
public static PlayerService ps;
And gets its value in onCreate of MainActivity
sc=new ServiceConnection(){
#Override
public void onServiceConnected(ComponentName p1, IBinder p2)
{
PlayerService.Getters getters=(PlayerService.Getters) p2;
ps=getters.getService();
}
#Override
public void onServiceDisconnected(ComponentName p1)
{
// TODO: Implement this method
}
};
Then PlayerService.Getters class is
public class Getters extends Binder
{
public PlayerService getService()
{
return PlayerService.this;
}
}
PlayerService has
#Override
public IBinder onBind(Intent p1)
{
return new Getters();
}
getService of Getters gives the object of PlayerService to my MainActivity.
Now I can get real time values of service variables and methods using static ps from multiple activities.
In order to send data or information from Service to Activity, you'll need to use Messenger API. This API will allow you to create an inter process communication (IPC) i.e. a communication link between two or more processes. In Android, Activity and Service are two separate processes, so you can use the IPC technique to establish a communication link in between them.
In the IPC technique, there are two ends, the Server end and the Client end. The Service acts as the Server and Activity acts as the Client.
Note: Service will only be able to communicate with one Activity at a time.
Messenger allows for the implementation of message-based communication across processes by help of Handlers.
Handler is a that allows you to send and process these messages.
Steps for implementing a Messenger:
Step 1. Service implements a Handler which receives the callbacks from the Activity
Step 2. The Handler then creates a Messenger object which further on creates an IBinder that the Service returns to the Activity.
Step 3. Activity then uses the IBinder to instantiate the Messenger, which the Activity uses to send messages to the Service.
Step 4. The Service receives the messages in the Handler created in the 1st step.
Lets now understand it with an example:
Create a Handler in the Service like this:
class ServiceHandler extends Handler {
#Override
public void handleMessage(Message msg) {
switch (msg.what) {
default:
super.handleMessage(msg);
}
}
}
Now, add the Messenger object along with onBind() method to the Service as mentioned in 2nd step above:
final Messenger messenger = new Messenger(new ServiceHandler());
#Override
public IBinder onBind(Intent intent) {
return messenger.getBinder();
}
In the Activity, we will create a ServiceConnection to fetch the iBinder from the Service to instantiate the Messenger object as mentioned in the 3rd step above.
Messenger messenger;
private ServiceConnection serviceConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder iBinder) {
messenger = new Messenger(iBinder);
}
public void onServiceDisconnected(ComponentName className) {
}
};
Bind the Service to the Activity by help of the ServiceConnection created above:
bindService(new Intent(this, MessengerService.class), serviceConnection,
Context.BIND_AUTO_CREATE);
To send messages to the Service from the Activity, use the send() method of the Messenger object.
If you want to receive messages from the Service in the Activity, you need to create a Messenger in the Activity along with a Handler and use the replyTo parameter of the Messenger to receive messages to the respective Handler.
This question already has answers here:
How can I update information in an Android Activity from a background Service
(5 answers)
Closed 9 years ago.
I have an app that receives gcm push notifications. I also have a check in place that if the app is currently open, it does not create the notification. In Android, is it possible in my push service to tell the current activity (IF Available/connected), that a new notification has arrived, refresh your list with the new content? If this is possible, I believe I am on the right path with using IBinders on my service. The thing about that is I am confused on how the Service Calls the Activity (I understand vice verse). Thanks in advance if anyone could help!
Just to be clear. I am trying to tell the activity about a new push message.
Service
public class LocalBinder extends Binder {
GcmIntentService getService() {
return GcmIntentService.this;
}
}
#Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IBinder mBinder = new LocalBinder();
Client (Activity)
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the service object we can use to
// interact with the service. Because we have bound to a explicit
// service that we know is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
mBoundService = ((GcmIntentService.LocalBinder)service).getService();
// Tell the user about this for our demo.
Toast.makeText(MainActivity.this, "Connected", Toast.LENGTH_SHORT).show();
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
// Because it is running in our same process, we should never
// see this happen.
mBoundService = null;
Toast.makeText(MainActivity.this, "Disconnected",
Toast.LENGTH_SHORT).show();
}
};
void doBindService() {
// Establish a connection with the service. We use an explicit
// class name because we want a specific service implementation that
// we know will be running in our own process (and thus won't be
// supporting component replacement by other applications).
bindService(new Intent(MainActivity.this,
GcmIntentService.class), mConnection, Context.BIND_AUTO_CREATE);
mIsBound = true;
}
void doUnbindService() {
if (mIsBound) {
// Detach our existing connection.
unbindService(mConnection);
mIsBound = false;
}
}
#Override
protected void onDestroy() {
super.onDestroy();
doUnbindService();
}
Yes it's possible.
In your service class have a variable of type YourActivity
MyActivity myActivity = null;
In your onServiceConnected method set this myActivity variable on the service, obtained through the binder.
mBoundService.myActivity = MyActivity.this; // or something similar
Now your service has a pointer to your activity!!! YEah!!
Inside your activity, create a function, the body of this function should refresh the UI with data.
Finally, when the service detects new data, call:
if (myActivity)
myActivity.refreshMyData();
When unbinding, remember to set the myActivity variable to null, otherwise the previous code will fail.
I have created a plugin that starts a service and this is working fine. However I wish to be able to send variables to the running service from the plugin, and to get variables out of the service. I have researched broadcast/receivers and binding but haven't be able to to get any examples working with the code structure I am using below. Does anyone have any tips? I'm new to android development and pretty new to Java (but not programming) so there is a conceptual leap that I haven't quite got yet.
Plugin
public class IOIOconnect extends CordovaPlugin {
private Context thiscontext;
private Intent ioioService;
// Handle calls from Javascript
#Override
public boolean execute(String action, JSONArray args, CallbackContext callbackContext) throws JSONException {
// Call from javascript to startup the IOIO service
if (action.equals("ioioStartup")) {
this.ioioStartup(callbackContext);
return true;
}
}
// Initialise IOIO service (Called from Javascript)
private void ioioStartup(CallbackContext callbackContext) {
// Initialise the service variables and start it it up
thiscontext = this.cordova.getActivity().getApplicationContext();
ioioService = new Intent(thiscontext, HelloIOIOService.class);
ioioService.putExtra("loadinterval", 800); // Set LED flash interval
thiscontext.startService(ioioService);
}
}
Service
public class HelloIOIOService extends IOIOService {
private int interval = 100;
#Override
public IBinder onBind(Intent intent) {
return null;
}
// USUAL IOIO SERVICE STUFF
#Override
public void onStart(Intent intent, int startId) {
// Service has been started
super.onStart(intent, startId);
// IOIO When service is started load external vars (if set)
int loadinterval = intent.getIntExtra("loadinterval", -1);
if(loadinterval>=0){ interval = loadinterval; }
// Native IOIO stuff
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
if (intent != null && intent.getAction() != null && intent.getAction().equals("stop")) {
// User clicked the notification. Need to stop the service.
nm.cancel(0);
stopSelf();
} else {
// Service starting. Create a notification.
Notification notification = new Notification(
R.drawable.ic_launcher, "IOIO service running",
System.currentTimeMillis());
notification
.setLatestEventInfo(this, "IOIO Service", "Click to stop",
PendingIntent.getService(this, 0, new Intent(
"stop", null, this, this.getClass()), 0));
notification.flags |= Notification.FLAG_ONGOING_EVENT;
nm.notify(0, notification);
}
}
}
I had exact same problem: writing plugin for Salesforce Mobile SDK (based on Cordova 2.3.0).
My case: plugin and app is different android projects.
The solutions, is that you have to publish the Service in AndroidManifest.xml of the main (app) project. Remember to sign it with full qualified path and as exported, like this:
<service
android:name="full.qualified.path.to.Service"
android:exported="true">
</service>
I managed to create a working plugin which is used in this project:
https://github.com/opensystemsassociation/southendtransportresearch/tree/master/phonegap
The code's not tidy as its still in development, but the 'hacky' approach works fine so hopefully it will help someone along the line a bit