Accessing BroadCastReceiver in Viewmodel - java

I am struggling in choosing the right way to pass data from broadcastReceiver to ViewModel and from there I pass data to my Repository and update LiveData. I use FCM push notifications and have local broadCastReceiver which uses ActivityLifecycle.
I found that it is bad practice to access ViewModel from BroadcastReceiver, but not sure why?
If I manage lifecycle of broadcastReceiver it should not cause any problems... So what is the best way to pass received data from FCM to my Repository's MediatorLiveData? I use MediatorLiveData, because I add different LiveData sources(API request and FCM).
Would be grateful for advice and correct way of implementing broadCastReceiver.
I have thought about accessing Repository from BroadCastReceiver, like this:
RepositoryMain.getSingletonInstance().setResponse(state);

You need to define single source of truth (SSoT). In your case it Repository (if Repository encapsulate db persistence storage, SSoT it is db). Now you need to implement data flow from receiver to view through SSoT (Repository) like in example below:
Receiver implementation
public class FcmReceiver extends BroadcastReceiver {
private final MutableLiveData<MyData> mData = new MutableLiveData<>();
public LiveData<MyData> getData() {
return mData;
}
#Override
public void onReceive(Context context, Intent intent) {
// entry point of data
mData.setValue(new MyData());
}
}
Repository
public class Repository {
private static final Repository INSTANCE = new Repository();
private final MediatorLiveData<MyData> mData = new MediatorLiveData<>();
private Repository() {}
public static Repository instance() {
return INSTANCE;
}
public LiveData<MyData> getData() {
return mData;
}
public void addDataSource(LiveData<MyData> data) {
mData.addSource(data, mData::setValue);
}
public void removeDataSource(LiveData<MyData> data) {
mData.removeSource(data);
}
}
View model
public class MyViewModel extends ViewModel {
public LiveData<MyData> getData() {
// for simplicity return data directly to view
return Repository.instance().getData();
}
}
Activity
There is binding of receiver data and Repository
public class MainActivity extends AppCompatActivity {
private FcmReceiver mReceiver;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReceiver = new FcmReceiver();
}
#Override
protected void onStart() {
super.onStart();
// add data source
Repository.instance().addDataSource(mReceiver.getData());
registerReceiver(mReceiver, IntentFilter.create("action", "type"));
}
#Override
protected void onStop() {
super.onStop();
// don't forget to remove receiver data source
Repository.instance().removeDataSource(mReceiver.getData());
unregisterReceiver(mReceiver);
}
}

I think you don't need to access the BroadCastReceiver within the viewmodel. Alternatively, use the BroadCastReceiver to move data between activities, if you want to do any logic to the data, send it to the viewModel related to that activity.
In Simple words:
Suppose we have the following components:
ActivityOne observes ViewModelOne
ActivityTwo observes ViewModelTwo
BroadCastReceiver [Send actions from ActivityOne]. ActivityTwo Listens to those actions
once ActivityOne receives data from the viewModelOne, it sends the data via the BroadCastReceiver.
ActivityTwo has registered for the BroadCastReceiver, thus it receives those actions, if it dose need to do any logic to the data, it can send it to the ViewModelTwo.

Related

Change TextView text from another class using interface

I'm trying to make some utils functions to use in a bigger app later(download file from url, upload file to url etc)
So in MainActivity I have only 2 buttons that on click call static methods from Utils class.
However, I want on MainActivity to have some indicators of how things working on download/upload methods(connecting, connection success/fail, percent of download etc) so I put on MainActivity a TextView that will show that. I made an interface ICallback that contains void setConnectionStatus(String status) and from Utils class I use this to send to MainActivity the status.
Here are some parts of the code :
public class MainActivity extends AppCompatActivity implements ICallback {
Button btnDownloadDB, btnUploadDB, btnUploadPics;
TextView txtStatus;
ProgressBar pb;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Initialize stuffs
initViews();
//Setting listeners
btnDownloadDB.setOnClickListener(v -> {
txtStatus.setText(R.string.connecting);
pb.setVisibility(View.VISIBLE);
Utils.downloadFile(DOWNLOAD_DB, DB_FILE_NAME);
});
}
#Override
public void setConnectionStatus(String status) {
Log.d("MIHAI", status);
txtStatus.setText(status);
}
The interface :
public interface ICallback {
void setConnectionStatus(String status); }
And the Utils class :
public class Utils {
static ICallback callback= new MainActivity();
public static void downloadFile(String downloadURL, String fileName) {
IFileTransferClient client = ServiceGenerator.createService(IFileTransferClient.class);
Call<ResponseBody> responseBodyCall = client.downloadFile(downloadURL);
responseBodyCall.enqueue(new Callback<ResponseBody>() {
#Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
Log.d("MIHAI", "connection ok");
callback.setConnectionStatus("Connection successful");
}
#Override
public void onFailure(Call<ResponseBody> call, Throwable t) {
Log.d("MIHAI", "err...fail");
callback.setConnectionStatus("Connection failed. Check internet connection.");
}
});
}
The problem appear on MainActivity, when I try to set text of the txtStatus TextView getting a null reference error even if the txtStatus is initialized on initViews() method.
The Logs are working fine so I get the right status in MainActivity. I tried to initialize the TextView again in that function before seting the text and im getting : "java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.pm.ApplicationInfo android.content.Context.getApplicationInfo()' on a null object reference
at android.content.ContextWrapper.getApplicationInfo(ContextWrapper.java:183)"
Is there any chance to make this work?
Thank you for reading.
Kind regards,
Mihai
There are multiple problems with your solution but the main one is this line:
static ICallback callback= new MainActivity();
First of all, never hold a static reference to Activity, Fragment, Context or any Context related classes. These classes are either bound to a Context or represent the Context itself. You may leak memory this way. But that is the other problem.
What is the actual problem in your code is that new MainActivity() in Utils class creates an absolutely different instance of MainActivity that has nothing to do with MainActivity that is responsible for displaying your UI in the runtime.
What you should do instead is pass an instance of ICallback to the function as an argument:
public static void downloadFile(String downloadURL, String fileName, ICallback callback) {
...
}
And remove static ICallback callback= new MainActivity();.
Note: when you pass a callback object to a function make sure when it is called your Activity is not in a finished state.

Android notification information not staying in java class ArrayList

I am creating an app that "Catches" all the notifications during a specified time period and then displays them all at one time. However, I am running into an issue with my NotificationListenerService Java class.
I currently am able to "Catch" the nofifications as they come through and stop them from displaying. I am also able to preserve the notification information in ArrayLists (as you can see in the onNotificationPosted method). However, when I try to use one of the ArrayList getters to pull the information into another class, the ArrayList is completly empty. Any thoughts as to why this is and why I can't pull this informaiton in another Java class?
NotificationListenerService Class
public class NotificationListenerServiceUsage extends NotificationListenerService {
private static final String TAG = "NotificationListenerSer";
ArrayList<Integer> idMap = new ArrayList<>();
ArrayList<Notification> notificationMap = new ArrayList<>();
#Override
public IBinder onBind(Intent intent) {
Log.d(TAG, "onBind: ");
return super.onBind(intent);
}
#Override
public void onNotificationPosted(StatusBarNotification sbn){
Integer notificationInt = sbn.getId();
Notification notificationContent = sbn.getNotification();
idMap.add(notificationInt);
notificationMap.add(notificationContent);
cancelAllNotifications();
}
#Override
public void onNotificationRemoved(StatusBarNotification sbn){
}
public ArrayList<Integer> getIdMap() {
return idMap;
}
public ArrayList<Notification> getNotificationMap() {
return notificationMap;
}
}
Implementation Class
public class Batch_Notifications extends AppCompatActivity implements AdapterView.OnItemSelectedListener {
public void getHeldNotifications(View view){
NotificationListenerServiceUsage noteListenerService = new NotificationListenerServiceUsage();
ArrayList<Integer> idMap = noteListenerService.getIdMap();
ArrayList<Notification> notificationMap = noteListenerService.getNotificationMap();
Log.d(TAG, "getHeldNotifications: " + idMap + notificationMap);
}
}
You can not persist data in runtime memory. Your NotificationListenerService will not always be running it will destroy and then again Instantiated and now all your properties will reinitialized.
The best way to this type of task is preserve data in a persistent Storage i.e Database. And when you try to send batch notification you get from the database. You can Use Sqlite database with Android-Room for easy implantation.
Have a look at https://developer.android.com/training/data-storage.

android:singleton instance not garbage collected after application is closed

I have an android activity with a fragment.
In the fragment, I fetch data using retrofit and set a static flag, so that , when I again go to this fragment, I restrict fetching data again.
I also store the data in a singleton instance.
But even after I destroyed the activity/closed the application, the static flag and the instance is still available and the list is also present in the instance, which malfunctions my app.
But I want the instance to be created newly and fetch data at each run.
This is my singleton instance.
public class Utilities {
private static Utilities utils = null;
private List<Data> friendsList;
public List<Data> getDataList() {
return dataList;
}
public void setDataList(List<Data> dataList) {
this.dataList = dataList;
}
private List<Data> dataList;
public synchronized static Utilities getInstance(){
if(utils == null){
utils = new Utilities();
}
return utils;
}
}
This is my fragment:
public class DataFragment extends Fragment
{
private static boolean hasObtainedData;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
if(!hasObtainedData){
getData(v);
}else{
recyclerView.setAdapter(new Adapter(utils.getDataList()));
}
}
private void getData(View v) {
//get Data using Retrofit:
hasObtainedData = true;
utils.setDataList(dataListObtainedUsingRetrofit)
recyclerView.setAdapter(new Adapter(utils.getDataList()));
}
}
This is how, I call my fragment from MainActivity:
#Override
public void onTabSelected(TabLayout.Tab tab) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragmentBox, new DataFragment()).commit();
}
I tried giving System.gc() at onDestroy() of MainActivity, but still, the singleton instance is alive.
I search many SOF posts based on this, but wasn't able to solve this issue.
Any help will be really useful.
The pointer to the Singleton is static in the Utilities-Class itself, so the Singelton can never be available for GC, unless you set utils = null on leaving MainAcitivity with something like
Utilities.reset();
Your singleton wont survive your app being terminated, and what's probably happening is that you aren't actually killing the app entirely during your tests. Run adb shell am force-stop <your-app-package> from your console and see whether that still results in the issue.
With that said, if you only want your data to run once per application launch, then I would recommend you move it into the onCreate() lifecycle callback of your Application
public class App extends Application {
#Override
public void onCreate() {
super.onCreate();
//Begin process to fetch data and cache it here
}
}
Don't forget to add the application name to your manifest too, <application android:name="your.packagename.App"
You then have no need to modify your data at all - it will only run once per application launch.
If, however, what you actually want is for your data to update every time the Activity is launched, then do the same process as before but inside onCreate() of you Activity. You can also clear it in onDestroy(), if you want it to updated when your Activity is recreated:
#Override
public void onDestry() {
super.onDestroy();
Utilities.getInstance().setDataList(null);
}
Also, if your data is bound to the lifecycle of your Activity, then you don't really need a singleton (which is bound to the lifecycle of the application).

Android : Inform adapter that data-set has changed from a static method

I am working on an Android project in which I am trying to integrate PUSH service offered by Cometd framework.
Now, whenever a new message arrives for a Conversation, I would like to inform ChatMessagesActivity which contains the list of messages between the two users.
Now, when the other user sends a message to the Android app, I would like to update the view of the user. I tried doing that by calling notifyDataSetHasChanged() on the adapter, but because I was calling it outside of View thread, I am getting an error.
The method is static, because new messages are received in Conversation class, while the messages are going-on in ChatMessagesActivity class. For communication between both classes, I have created 2 static methods which act like a bi-directional bridge for sending & receiving messages.
I hope I was clear, if there are any doubts, kindly let me know.
ChatMessagesActivity.java :
public class ChatMessagesActivity extends ApplicationDrawerLoader {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_chat_messages);
chatList = (ListView)findViewById(R.id.chatList);
new getPrivateChatsForUser(this).execute();
}
// IN the method below, I receive information from another activity.
public static void recieveUpdatedMessage(String channelName, Map<String, Object> input){
Boolean found = Arrays.asList(channelName.split(" ")).contains("chat");
if(found){
int processedChannelName = Integer.valueOf(channelName.replace("/chat/",""));
if(processedChannelName == groupAccountId){
// Here i tried calling adapter.NotifyDataSetchanged();.. Didn't fly.
}
}
}
// Async method retrieving messages.
public class getPrivateChatsForUser extends AsyncTask<Void, Void, ResponseEntity<RestReplies[]>> {
ChatMessagesActivity chatMessagesActivity = null;
getPrivateChatsForUser(ChatMessagesActivity chatMessagesActivity) {
this.chatMessagesActivity = chatMessagesActivity;
}
#Override
protected ResponseEntity<RestReplies[]> doInBackground(Void... params) {
// network connection related stuff, not relevant to problem
}
#Override
protected void onPostExecute(ResponseEntity<RestReplies[]> responseEntity) {
super.onPostExecute(responseEntity);
RestReplies[] restRepliesArray = responseEntity.getBody();
Collections.addAll(restRepliesList, restRepliesArray);
ArrayList<HashMap<String, String>> chatMessagesHashMapList = new ArrayList<HashMap<String, String>>();
for (RestReplies restReplies : restRepliesList) {
HashMap<String, String> chatMap = new HashMap<>();
chatMap.put(chatText, restReplies.getReplyText());
chatMap.put(firstName, restReplies.getReplyingPersonName());
chatMap.put(chatImage, restReplies.getSenderImage());
chatMap.put(privateChannel,"/service/person/"+String.valueOf(conversationId));
chatMessagesHashMapList.add(chatMap);
}
chatList = (ListView) findViewById(R.id.chatList);
chatMessagesAdapter = new ChatMessagesAdapter(chatMessagesActivity, chatMessagesHashMapList);
chatList.setAdapter(chatMessagesAdapter);
chatList.scrollTo(0, chatList.getHeight());
}
}
So, how should I instruct that the data-set has been changed.. And how does the adapter knows where and how to get the data-set which has changed. Can anyone help me with this problem. Thanks a lot... :-)
Use broadcast receiver at your adapter and fire a local broadcast with android LocalBroadcast in your push service
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent)
{
if(intent.getAction().equals("MYREFRESH"))
{
notifiyDataSetChanged();
}
}
};
In your constructor in adapter register this reciever
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction("MYREFRESH");
LocalBroadcastManager.getInstance(context).registerReceiver(broadReceiver, intentFilter);
In your push if you get a push notification trigger this
Intent intent = new Intent();
intent.putExtra(...) //send any data to your adapter
Intent.setAction("myaction");
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
The way to deal with this is using a broadcast or bus pattern. You can use some good bus libraries such as http://square.github.io/otto/ or a LocalBroadcast
This past answer from me shows how to use the LocalBroadcast system: Refreshing fragments in FragmentActivity after sync service runs

How to play android sounds NOT in activity class

How can I play sound from a class that DOES NOT extend activity? I've been searching for a while and in every tutorial or answers from stackoverflow I've seen that sounds are always implemented in an activity class.
But in this case I have a class thas has the logic of my game, in which I have a gameUpdate() function; and in that function I want to make a specific sound play if something happens (for example a collision). How can I possibly access the activity that is currently running, from this class? Is there any way to do that?
Thanks.
If you need to get the current Activity instance or context you need to pass it to your other classes so that you can use it. For example:
class ABC extends Activity
{
public void onCreate(Bundle b)
{
XYZ xyz=new XYZ(this); // Sending the current Activity instance
}
}
class XYZ
{
ABC activity;
public XYZ(ABC activity)
{
this.activity = activity; //Now you can use it in this class
}
}
getActivity() or if is inside a fragment getFragment().getActivity()
Or alternativelly you can make add a Context to your class and get the activity reference from the constructor of the class.
Ex:
public class MyClass {
Context mContext();
public MyClass(Context context){
this.context = context;
}
}
and in your Activity class when you call MyClass:
MyClass myClass = new MyClass(this);
Inside your custom lass you can reference activity methods using its context.
So you actually just need a Context, not specifically an Activity (which is a Context). I would recommend that the class that should play sounds has a constructor which requires a Context. Keep a reference, not directly to the Context that you receive, but to the Application context using getApplicationContext() to get a Context that is safe to retain without the risk of memory leaks.
public class MySoundPlayingClass {
private final Context mContext;
public MySoundPlayingClass(Context ctx) {
// Since ctx could be an Activity, and this class
// could exist outside of the lifecycle of the Activity,
// grab the Application context to get a safe reference.
mContext = ctx.getApplicationContext();
}
}
Have a Util class and do something similar to below one. You can pass the context (it can be Activity instance) and the resource id to play it
Usage:
Util.play(context, R.raw.soundFile);
Sample Util class:
public class Util {
public static void play(final Context context, int resource) {
MediaPlayer mp = MediaPlayer.create(context, resource);
if (null != mp) {
mp.setOnCompletionListener(new OnCompletionListener() {
#Override
public void onCompletion(MediaPlayer mp) {
mp.release();
}
});
mp.start();
}
}
}

Categories