How to use Room Database inside BroadcastReceiver? - android

Good day. My app needs to receive SMS and what I am trying to do right now is I want to insert the data to the database from BroadcastReceiver as soon as the app received the message but I failed. Here is my code.
public class SmsReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ViewModel model = ViewModelProviders.of((FragmentActivity) context).get(ViewModel.class);
Bundle bundle = intent.getExtras();
Object[] objects = (Object[]) Objects.requireNonNull(bundle).get("pdus");
SmsMessage[] smsMessages = new SmsMessage[Objects.requireNonNull(objects).length];
Sms sms = new Sms();
for (int i = 0; i < smsMessages.length; i++) {
smsMessages[i] = SmsMessage.createFromPdu((byte[]) objects[i]);
sms = new Sms(
smsMessages[i].getOriginatingAddress(),
smsMessages[i].getMessageBody(),
smsMessages[i].getTimestampMillis()
);
}
model.insert(new ReplyToVotersQueue(sms.getBody(),sms.getAddress()));
}
}
As we can see, I am trying to insert the data to database but I've got an error that says
java.lang.RuntimeException: Unable to start receiver
mgb.com.smspoll.Services.SmsReceiver: java.lang.ClassCastException:
android.app.ReceiverRestrictedContext cannot be cast to
androidx.fragment.app.FragmentActivity
I found from the internet while searching the answer to this problem that BroadcastReceiver cannot convert to Activity because BroadcastReceiver can run without context. If that so then is there any
way to insert the data to database using room database after receiving the SMS? Since my app is an SMS Poll, I need to run my app in the background process.

The problem here is that you're trying to retrieve the ViewModel outside an activity or a fragment, which is not a good practice and also doesn't work.
The solution is this:
Separate your Room DB operation from ViewModel in a separate singleton class. Use it in ViewModel and any other place required. When Broadcast is received, write data to DB through this singleton class rather than ViewModel.
For more, please take a look at this post:
The correct way to obtain a ViewModel instance outside of an Activity or a Fragment

Related

How do I pass data from Service to Activity in Kotlin?

Beginner warning although CS student
I have a Service named RestApiService and an activity named MainActivity.
I have a token which I got stored in RestApiService.
In link is screenshot of the RestApiService in imgur
https://imgur.com/a/Msu04xj
I only find tutorials in Java and I can´t translate.
How do I get it from there to MainActivity? TIA
A way to do this is by using some database, store it there and then you can just observe the database on MainAcitivty level to get that value once its available.
An alternative would be to use shared preferences liveData which you will observe on MainActivity again but I guess that's a bit trickier
I would go with creating an entity which you will save in database using Room and then use a ViewModel component to expose liveData to the MainActivity
Easiest way- bind to the service, and define a Binder for the service with a function to get the value. Then you can just call that function to get the token. The Binder is returned to the Activity as the return from bindService().
Here's Google's example of doing so in kotlin: https://developer.android.com/guide/components/bound-services#kotlin
BroadCastReceiver will help you, in your service send a broadcast intent and in your activity you will receive your data.
You can use Local BroadcastReceiver for sending data from service to Activity
Please try this code in your RestApiService class where you get token:
val intent = Intent("RestApiData")
intent.putExtra("token", myToken)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
In above code "RestApiData" is main key of broadcast receiver that you need for getting data in activity, "token" is key for myToken value
In Your MainActivity class use below code in onCreate method:
LocalBroadcastManager.getInstance(this).registerReceiver(tokenPassingReceiver, IntentFilter("RestApiData"))
Now use below code outside the onCreate method of activity:
private val tokenPassingReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val bundle = intent.extras
if (bundle != null) {
if (bundle.containsKey("token")) {
val tokenData = bundle.getString("token")
Log.e("MainActivity--","token--$tokenData")
}
}
}
}
For more information check this link: Kotlin Android Broadcast Intents and Broadcast Receivers
I hope it may help you.

Accessing MainActivity once BOOT_COMPLETE is received

I have a reminder app that will notify me at a given time when the next reminder from an array of objects it due.
I am trying to make it set the notification again on boot.
I have my boot receiver all set in the Manifest, but how do I access any information from MainActivity once the phone has booted, given that the app hasn't been opened yet?
I was hoping to use this -
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.i("RegularReminders","onReceive");
new MainActivity().setNotifications();
}
}
But it returns a null error from within that notification once it tries to run the method in MainActivity, the app crashes as the emulator boots up and I see this in the logcat -
java.lang.RuntimeException: Unable to start receiver com.androidandyuk.regularreminders.BootReceiver: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.util.ArrayList.size()' on a null object reference
This points to a line -
if (reminders.size() >= 0) {
I did wonder if I could save the notification message to SharedPrefs and call it back in the receiver, but I got errors of null object reference doing that too.
I tried sending back another broadcast adding extra info, but I guess the receiver set up in MainActivity isn't listening as the app hasn't been run?
I know Google is protecting us from Malware, not letting them do much after book, but is there any way round this so I can set my notification after a reboot?
Thanks.
Although you can't access variables from the MainActivity, you can access SharedPreferences and the SQLite Database used in the app, so in this case I made a new array and read the database into it and worked on that.
The problem is that your activity is not created when you receive ACTION_BOOT_COMPLETED.
What you could do is to start your activity after the boot is completed and pass it arguments to tell it do some work.
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.i("RegularReminders","onReceive");
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Intent i = new Intent(context, YourActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle args = new Bundle();
//put some args inside the bundle
//attach the bundle
i.putExtras(args);
//start the activity
context.startActivity(i);
}
}
}
If you don't what to start and show your activity. You can use Android Services.

Android: how to use ContentResolver in background

I have class that extends BroadcastReceiver and gets all new sms.
When I get new sms I want to search the phonebook to check if phone numbers inside this message are in phone book, and if they are, send sms with result to another phone.
I use ContentResolver to check the phone numbers, but to use it I need to use it inside Activity or have pointer to activity, but my sms listener works in background.
Is there a way to search phone book without activity or to get valid activity from context or intent that I get when recieving new sms?
I tried to use something like that:
Activity act = new Activity();
DBManager dbm = new DBManager(act);
ArrayList<MyContact> res = dbm.phoneSearch(sms_ar[1]);
SmsSender sms = new SmsSender(act);
if(res.size() > 0){
String answer = res.get(0).getName()+CSStatic.SMS_SEPARATOR+res.get(0).getPhone();
for(int z = 1; z < res.size(); z++){
answer = answer+CSStatic.SMS_SEPARATOR+res.get(z).getName()+CSStatic.SMS_SEPARATOR+res.get(z).getPhone();
}
Log.d("sms", "Answer: "+answer);
sms.smsAnswer(answer, sms_ar[2]);
} else {
String phone = dbm.getPhoneToQuery(sms_txt);
sms.smsQueryNext(sms_txt, phone);
}
act.finish();
but it don't work :P
In DBManager I search for phones and in SmsSender I send new sms.
You need a valid Context object (an activity or service for example, these are subclasses of Context). These are created by android, do not use the constructor to create these objects!
To get the needed context in a BroadcastReceiver is explained here: https://stackoverflow.com/a/6074829/1127492, its the first parameter of onReceive().

When my app is not running (only when not running), why does my BroadcastReceiver give me a nullpointer exception?

I will try to sum up simply because my original code is long. Suppose that My broadcast receiver is like this :
public class SMSBroadcastReceiver extends BroadcastReceiver {
static ArrayList<String> phonenumber_array = new ArrayList<String>();
static ArrayList<String> message_array = new ArrayList<String>();
#Override
public void onReceive(Context context, Intent intent) {
Bundle bundle = intent.getExtras();
if (bundle != null) {
Object[] pdus = (Object[]) bundle.get("pdus");
SmsMessage[] sms = new SmsMessage[pdus.length];
for (int i = 0; i < sms.length; i++) {
sms[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
phonenumber_array.add(0,sms[i].getOriginatingAddress());
message_array.add(0,sms[i].getMessageBody());
com.test.ListActivityClass.myadapter.notifyDataSetChanged();
}
}
In my AndroidManifest.xml, The BroadcastReceiver has the IntentFilter "android.provider.Telephony.SMS_RECEIVED".
My ListActivityClass extends ListActivity, contains a myadapter which is an ArrayAdapter, like : myadapter = new CustomAdapter (this,com.test.SMSBroadcastReceiver.phonenumber_array,com.test.SMSBroadcastReceiver.message_array)
So my problem is : I send an sms from eclipse to emulator. whenever myadapter.notifyDataSetChanged(); is called in my BroadcastReceiver when my application is not running(completely stoped, no process launched, no activities) , I get a nullpointer exception.
When I remove myadapter.notifyDataSetChanged(); the problem is gone.
When the app is running, there is no problem as well, my application gets correctly the new sms received and displays it in the list.
So my guess was = When the app is running, the BroadcastReceiver can detect the myadapter, because the app is in memory, so it can notify changes. when the app isn't running, it is not in memory so BroadcastReceiver can't see any myadapter, so it gives a nullpointer exception. it makes sense.
So I tried to check : if the process is in memory, notify changes, else, don't notify ( because my applications gets all the sms received onCreate(), so it is not necessary.
ActivityManager activityManager = (ActivityManager) context
.getSystemService(context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> runningProcInfo = activityManager
.getRunningAppProcesses(); for (int i1 = 0; i1 <
runningProcInfo.size(); i1++) {
if (runningProcInfo.get(i1).processName.equals("com.test")) {
com.test.ListActivityClass.myadapter.notifyDataSetChanged();
}
But nothing changed. I still have a nullpointer exception when app is not running.
The worst thing is, I tried to replace phonenumber_array by phonenumber_array.add(0,"somephonenumber"); and message_array.add(0,"somemessage"); to check if a BroadcastReceiver runs in the backround even if the app is closed, my thoughts were correct. After the null pointer exception, I launched the app, and somephonenumber and somemessage were both present.
What do I miss? Does a BroadcastReceiver runs in background, or only when application launches? what is wrong?
First, static data members (and the comment-suggested replacement, Application) only live as long as your process lives. Your process may go away microseconds after onReceive() completes.
If you want to use static data members as a cache, that's fine, but durable data needs to live on the filesystem (database, flat file, etc.).
Second, myadapter should not be a static data member. You are leaking memory.
Either:
Move this BroadcastReceiver to be registered by the activity via registerReceiver(), or
Have this BroadcastReceiver send another ordered broadcast to be picked up by the running activity (if it is in the foreground), as is described in this blog post and is demonstrated in this sample project
You can create static boolean variable onScreen and set it to true in onResume method, and set to false in onPause method. It is the easiest way to understand if application is on the screen

Android Broadcast from Service To Activity

I am trying to send a Broadcast from a service out to an Activity. I can verify the broadcast is sent from within the service, but the Activity doesn't pick up anything.
Here is the relevant service code:
Intent i = new Intent(NEW_MESSAGE);
i.putExtra(FriendInfo.USERNAME, StringUtils.parseBareAddress(message.getFrom()));
i.putExtra(FriendInfo.MESSAGE, message.getBody());
i.putExtra("who", "1");
sendBroadcast(i);
And the receiving end in the activity class:
public class newMessage extends BroadcastReceiver
{
#Override
public void onReceive(Context context, Intent intent)
{
String action = intent.getAction();
if(action.equalsIgnoreCase(IMService.NEW_MESSAGE)){
Bundle extra = intent.getExtras();
String username = extra.getString(FriendInfo.USERNAME);
String message = extra.getString(FriendInfo.MESSAGE);
String who = extra.getString("who");
}
}
}
The BroadcastReceiver is defined within an Activity. I am registering the receiver in the onCreate method of the Activity, not in the Manifest file.
I'm stumped as to why it won't rec. anything.
Any insight?
EDIT
Registering takes place as follows:
registerReceiver(messageReceiver, new IntentFilter(IMService.NEW_MESSAGE));
Where "messageReceiver" is defined as
private newMessage messageReceiver = new newMessage();
IMService.NEW_MESSAGE is merely a string = "NewMessage"
I'm not sure if it is specific to the set up, or if it is a fix in general, but moving the register/unregister to the onResume/onPause _respectively_ and not registering in the onCreate solved the problem for me.
Try this two things:
Use manifest file to register receiver(but it barely helps)
Try make your Receiver a regular class, not inner one.
Inner class broadcast receiver will not be able to handle broadcast(means it unable to locate that class ).
So make it as a separate class
Definitely it will work.

Categories

Resources