My interface:
public interface LoginDialogDismissListener {
void loginDialogCancel();
void loginDialogSuccess();
}
My activity:
public class MainActivity extends Activity implements LoginDialogDimissListener {
public void loginDialogCancel() {
//do stuff here
}
public void loginDialogSuccess() {
//do some other stuff here!
}
}
My LoginDialog:
public class LoginDialog extends Dialog implements OnClickListener, LoginChecker {
private ProgressBar pbLogin;
private TextView tvLoginstatus;
private Button cancel;
private Button save;
private EditText username;
private EditText password;
public LoginDialog(Context context) {
super(context);
setContentView(R.layout.login_dialog);
pbLogin = (ProgressBar) findViewById(R.id.progressBarLogin);
tvLoginstatus = (TextView) findViewById(R.id.dialogTvLoginstatus);
cancel = (Button) findViewById(R.id.bDialogCancel);
save = (Button) findViewById(R.id.bDialogSave);
username = (EditText) findViewById(R.id.dialogEtUsername);
password = (EditText) findViewById(R.id.dialogEtPassword);
setTitle("Brukerdata");
pbLogin.setVisibility(View.INVISIBLE);
tvLoginstatus.setVisibility(View.INVISIBLE);
cancel.setOnClickListener(this);
save.setOnClickListener(this);
//setOnDismissListener(this);
}
public void onClick(View v) {
if (v.getId() == R.id.bDialogSave) {
saveClick();
}
else if (v.getId() == R.id.bDialogCancel) {
cancelClick();
}
}
private void saveClick() {
save.setEnabled(false);
String[] credentials = {username.getText().toString(), password.getText().toString()};
pbLogin.setVisibility(View.VISIBLE);
CheckLoginTask logintask = new CheckLoginTask(this, credentials, 0);
logintask.execute();
}
private void cancelClick() {
this.dismiss();
//Here I want to call LoginDialogDismissListener.loginDialogCancel()
}
//Called from my AsyncTask (CheckLoginTask)
public void onLoginSuccess(int requestCode) {
this.dismiss();
//Here I want to call LoginDialogDismissListener.loginDialogSuccess()
}
//Called from my AsyncTask (CheckLoginTask)
public void onLoginFail(int requestCode) {
pbLogin.setVisibility(View.INVISIBLE);
tvLoginstatus.setText("Feil brukernavn/passord ...");
tvLoginstatus.setVisibility(View.VISIBLE);
save.setEnabled(true);
}
}
If you have read my comments in the code, you see that I want to call my MainActivity with the methods it implements from my interface. The thing is that I can't figure out how to send my activity only once to my LoginDialog.
I mean, first it needs the Context, second I would like to provide it LoginDialogDismissListener (should probably change that name...), which is my activity.
I feel there is a better solution than this:
//Constructor
public LoginDialog(Context context, LoginDialogDismissListener listener) {
}
//Activity creating the Dialog
new LoginDialog(this, this);
I have tried to define the constructor to only have LoginDialogDismissListener as argument, but I am not allowed to cast it or somehow get the context that I need for my super(context)
Answer from #android-dev
Make a parent Activity that implements my interface and have all activities extend that again.
Don't use "this", use getApplicationContext().
Related
I have a newbie question about Class/Task/Activity. I'm coming from C so I don't know if it's a good approach to do what I need.
I've created a class:
public class UDPServer {
private MyDatagramReceiver myDatagramReceiver = null;
private static int MAX_UDP_DATAGRAM_LEN = 1024;
private static int UDP_SERVER_PORT = 5000;
public void start() {
myDatagramReceiver = new MyDatagramReceiver();
myDatagramReceiver.start();
}
public void kill() {
myDatagramReceiver.kill();
}
private class MyDatagramReceiver extends Thread {
private boolean bKeepRunning = true;
private String lastMessage = "";
public void run() {
String message;
byte[] lmessage = new byte[MAX_UDP_DATAGRAM_LEN];
DatagramPacket packet = new DatagramPacket(lmessage, lmessage.length);
DatagramSocket socket = null;
try
{
socket = new DatagramSocket(UDP_SERVER_PORT);
while(bKeepRunning)
{
socket.receive(packet);
message = new String(lmessage, 0, packet.getLength());
lastMessage = message;
//Here should call activity method
});
}
}
catch (Throwable e)
{
e.printStackTrace();
}
finally
{
if (socket != null)
{
socket.close();
}
}
}
public void kill() {
bKeepRunning = false;
}
}
}
Inside my Activity I've:
#Override
public void onResume() {
super.onResume();
mUDPServer = new UDPServer();
mUDPServer.start();
}
#Override
public void onPause() {
super.onPause();
mUDPServer.kill();
}
Now, every time I received a packet I want that this thread/class pass received packet to an Activity method that elaborate(do some calculation or update some UI ecc..) this incoming data. But I can't figure how to do this, maybe my approach is not correct. I can place thread code inside Activity but it seems to make code less readable.
Suggestion how to do this?
#Anshul Jain CODE UPDATE:
public class Main_activity extends Activity implements Interface_UDPServer{
TextView recived_message;
UDPServer mUDPServer;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
recived_message = (TextView)findViewById(R.id.recived_message);
}
#Override
public void onResume() {
super.onResume();
mUDPServer = new UDPServer(this);
mUDPServer.start();
}
#Override
public void onPause() {
super.onPause();
mUDPServer.kill();
}
public void sendData(final String str){
runOnUiThread(new Runnable() {
#Override
public void run() {
recived_message.setText(str);
}
});
}
}
XML file
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_below="#+id/linearLayout"
android:layout_alignLeft="#+id/linearLayout"
android:layout_alignStart="#+id/linearLayout">
<TextView
android:id="#+id/recived_message"
android:layout_width="200dp"
android:layout_height="35dp"
android:textColor="#444444"
android:textSize="20dp" />
</LinearLayout>
</RelativeLayout>
You can use Callback for this purpose.
Define some interface like
public interface MyCustomInterface(){
public void sendData(String str);
}
Now let your Activity implement this interface.
public class MyActivity implements MyCustomInterface {
#Override
public void sendData(String str){
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
recived_message.setText(str);
}
});
}
}
Now in UDPServer.java, write the following code
public class UDPServer {
private MyCustomInterface interface;
UDPServer(MyCustomInterface interface){
this.interface = interface;
}
}
Now whenever you have some data available lets say a string, you can send it like this
interface.sendData(str);
You have an A activity and B one, when you finish actions on B activity side you need it to effect A side when you come back.
Create an Instance Class and a method that u type u need, let's say;
public interface SelectedBirthday {
void onSelectedData(String date);
}
Now we are on B side, Create an instance of your Interface Class
private SelectedBirthday mCallback;
Override
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (SelectedBirthday) activity;
} catch (ClassCastException e) {
Log.d("MyDialog", "Activity doesn't implement the ISelectedData interface");
}
}
Now upload the value you needed
String userBirth = (day + " " + month + " " + year);
mCallback.onSelectedData(userBirth);
Ok let's go to A side
Implement our Interface Class
implements SelectedBirthday
it will warn you for its method and you implemented it
#Override
public void onSelectedData(String date) {
if (!date.equals("")) {
txt_poup_age.setText(date);
//now you are free to do what you want with the value you received automaticaly
}
}
In android 4 option to do this
In android you can send data through Intent or Intent followed by Bundle.Like
Intent i = new Intent(current_class.this, linked_class.class);
i.putextra("Key", value);
And get the value(suppose string value) in another class like:
String value = getIntent.getExtra("String key which you used when send value");
option 2
class A{
public static String _utfValue = "";
void sendValue(){
_utfValue = "some value";
}
}
And fetch this value in your java class like:
String value = A._utfValue ;
You can use SharedPreference to save the value and get it from other class.
You can use a static method with some return value and fetch the method in your java class through class name.
I am sharing the code as suggested by #Shadman Akhtar and reply by #Singee which worked .
As soon as the button is clicked the Textview value is set with the value you want to send to MainActivity from retrivedata.java .I haved used Button to simulate the Asynchronous methods which can be inside retrivedata.java
MainActivity.java
package com.example.interfacetesting;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends AppCompatActivity implements MyCustomInterface {
TextView tv1;
Button btn1;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv1=(TextView)findViewById(R.id.tv1);
btn1=(Button)findViewById(R.id.btn1);
btn1.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
retrivedata rd=new retrivedata(MainActivity.this);
rd.recievedataa();
}
});
}
#Override
public void sendData(String str){
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
#Override
public void run() {
tv1.setText(str);
}
});
}
}
MyCustomInterface.java //this is interface
package com.example.interfacetesting;
public interface MyCustomInterface{
public void sendData(String str);
}
retrivedata.java //class from which data will be sent to MainActivity.
package com.example.interfacetesting;
public class retrivedata{
private MyCustomInterface otherNameInterface;
retrivedata(MyCustomInterface otherNameInterface){
this.otherNameInterface = otherNameInterface;
}
void recievedataa()
{
otherNameInterface.sendData("---any string you want to send to mainactivity-------");
}
}
Maybe you could use a Handler, load it with some data, and then read those data from your activity. Check more infos here about handlers
You'd just pass an handler from your activity to your class, use handler.sendMessage("") inside your run method, and analyse what you receive inside your activity.
In your case I would use activity as interface. The interface is stored as a static parameter inside Application class.
there are many ways you can achieve this following are some
you can use interfaces as explained in one of the answers
you can create a IntentServce instead of your class and use Result Receiver to communicate the data.
you can use handlers and messages as well
you can also create a service and use IBinders (Bound Service)
you can Google out more about these methods and chose what suits you better
I've noticed a bug in a basic survey app I'm making to better learn android.
Occasionally I get a W/System.err﹕ at MainActivity.surveyAvailable(MainActivity.java:40) that points to this line of code:
button.setVisibility(View.GONE);
I've used setVisibility many times before and never had any issues.
Here's the function, this gets called when the user first enters the app, and after they finish taking a survey to check the server and see if there is another survey available for the user:
public void surveyAvailable(boolean surveyIsAvailable) {
Log.d("MainActivity", "App survey is available? " + surveyIsAvailable );
Button button = (Button)findViewById(R.id.takeSurveyButton);
if (surveyIsAvailable) {
button.setVisibility(View.VISIBLE);
button.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
App.getInstance().showSurvey();
}
});
} else {
Log.d("MainActivity", "We hit here");
button.setVisibility(View.GONE);
}
}
When a survey isn't available, the appropriate lines are logged - App survey is available? false and 'We hit here'. But then the button sometimes doesn't get set to View.GONE and I see the System.Err line. But sometimes it works fine and the button's visibility does change. Any idea how to fix that? Or how to get more information on what the System.Err actually means?
EDIT:
I found that by setting Button surveyButton; in my activity and then referencing the button as this.surveyButton seems to get the functionality to work more along the lines of what we'd expect (e.g. when we call button.setVisibility(View.GONE) the view is actually consistently GONE). But it still throws the System.Err line which has me hesitant that things are working correctly.
Edited Activity:
public class MainActivity extends ActionBarActivity implements SurveyListener {
Button surveyButton;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
this.surveyButton = (Button)findViewById(R.id.takeSurveyButton);
}
public void surveyAvailable(boolean surveyIsAvailable) {
Log.d("MainActivity", "App survey is available? " + surveyIsAvailable );
if (surveyIsAvailable) {
this.surveyButton.setVisibility(View.VISIBLE);
this.surveyButton.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
App.getInstance().showSurvey();
}
});
} else {
Log.d("MainActivity", "We hit here");
this.surveyButton.setVisibility(View.GONE);
}
}
}
The activity implements this class:
public abstract interface SurveyListener
{
public abstract void surveyAvailable(boolean surveyAvailable);
}
Main App class that checks for surveys and calls 'surveyAvailable()`:
public class App
{
private static App _instance;
private SurveyListener _eventsHandler;
private String _apiKey = "";
private String _appuserId = "";
private String _surveyUrl = "";
private Activity _parentContext;
private Boolean _surveyAvailable;
public static App initWithApiKeyAndListener(String apiKey, SurveyListener surveyEventsHandler) {
if (_instance == null)
{
_instance = new App();
_instance._parentContext = (Activity) surveyEventsHandler;
_instance.setSurveyListener(surveyEventsHandler);
_instance.setApiKey(apiKey);
String appuserId = PreferenceManager.getDefaultSharedPreferences((Activity) _instance._eventsHandler).getString(tag, "no_appuser");
if (appuserId == "no_appuser") {
_instance._surveyAvailable = true;
_instance.alertAvailability(true);
} else {
_instance.checkForCampaigns();
}
}
return _instance;
}
private void alertAvailability(boolean surveyAvailable) {
App.getInstance()._eventsHandler.surveyAvailable(surveyAvailable);
}
private void checkForCampaigns() {
new CampaignCheck().execute();
}
public static App getInstance()
{
if (_instance == null)
{
_instance = new App();
}
return _instance;
}
public void donePushed()
{
App.getInstance().checkForCampaigns();
}
private class CampaignCheck extends AsyncTask<Void, Void, Void> {
protected Void doInBackground(Void... params) {
Boolean surveysAvailable = false;
try {
surveysAvailable = new AppuserConnection().checkCampaigns();
App.getInstance()._surveyAvailable = surveysAvailable;
App.getInstance().alertAvailability(_surveyAvailable);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(Void result) {
}
}
}
You shouldn't modify the UI elements from a different thread. You are doing this by calling App.getInstance().alertAvailability(_surveyAvailable); on a background thread. Move this to the AsyncTask's onPostExecute.
recently i opened a question about how to call a method in another class... ok, i did call the example method, but the method i really want to call isn't working:
I have 2 classes:
I want to call the method here: TelaCadastroRestaurante.java
I have the method here: Metodos.java
http://i.imgur.com/JVMjz8J.png
http://i.imgur.com/IuBTmCY.png
public class TelaCadastroRestaurante extends Activity {
private EditText nomeRestaurante, emailRestaurante, telefoneRestaurante;
private Button buttonProximo;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_tela_cadastro_restaurante);
incializarComponentes();
acaoBotoes();
}
public void incializarComponentes() {
nomeRestaurante = (EditText) findViewById(R.id.editTextNomeRestauranteTelaCadastroRestaurante);
emailRestaurante = (EditText) findViewById(R.id.editTextEmailRestauranteTelaCadastroRestaurante);
telefoneRestaurante = (EditText) findViewById(R.id.editTextTelefoneRestauranteTelaCadastroRestaurante);
buttonProximo = (Button) findViewById(R.id.buttonProximoTelaCadastroRestaurante);
}
public void acaoBotoes() {
buttonProximo.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
Metodos metodos = new Metodos();
Metodos.taskInProgres(true, getApplicationContext());
}
});
}
public void pegarValores(){
Restaurante rest = new Restaurante();
rest.setNomeRest(nomeRestaurante.getText().toString());
rest.setEmailRest(emailRestaurante.getText().toString());
rest.setTelefoneRest(Integer.parseInt(telefoneRestaurante.getText().toString()));
Toast.makeText(getApplicationContext(), rest.getNomeRest() + "\n" + rest.getEmailRest() + "\n" + rest.getTelefoneRest(), Toast.LENGTH_SHORT).show();
}
}
CLASS WITH METHODS
public class Metodos {
private static ProgressDialog dialog;
public static void taskInProgres(boolean mostrar, Context context) {
if (dialog == null) {
dialog = new ProgressDialog(context);
dialog = ProgressDialog.show(context, "","Aguarde a verificação...", true);
}
if (mostrar) {
dialog.show();
} else {
dialog.dismiss();
}
}
}
In your xMetodos.java class change the method to be static :
private static void... and in TelaCadastroRestaurante.java class call it like this :
xMetodos.taskInProgres(true,getApplicationContext());
If you declared method as static you can call it without instantiating it.
You have to make an instance of class xMethodos to be able to call the method.
Code would look like:
xMethodos foo = new xMethodos ():
foo.taskInProgress([YOUR_BOOL], [YOUR_CONTEXT);
Where the context would probably your activity, which can be reffered to as this.
I have a class which has a static inner class. The OuterClass wants to use the variables from the static inner class. The problem is I need to instantiate the inner class if I'm using instance variables. So, I decided to use a static variables. Does it contrary to OOP concepts? If so, is there any other principle I should follow or any design pattern I should use to do the same thing?
The reason I used static class is I want to create a custom builder for android activity. The problem is I cannot use constructor to initialise OuterClass which extends Activity. So, I need to load those static variables inside the onCreate() method.
This is my sample code
public class DialogFactory extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().setBackgroundDrawable(
new ColorDrawable(android.graphics.Color.TRANSPARENT));
setContentView(R.layout.activity_custom_dialog);
this.setDialogTitle(Builder.title);
this.setDialogMessage(Builder.message);
this.loadButtons();
}
public void onClick(View view) {
switch (view.getId()) {
case R.id.dialog_positive_button:
Builder.callable.onClickButton(Builder.type, DialogEventListener.ButtonType.POSITIVE_BUTTON);
this.finish();
break;
case R.id.dialog_neutral_button:
Builder.callable.onClickButton(Builder.type, DialogEventListener.ButtonType.NEUTRAL_BUTTON);
this.finish();
break;
case R.id.dialog_negative_button:
Builder.callable.onClickButton(Builder.type, DialogEventListener.ButtonType.NEGATIVE_BUTTON);
this.finish();
break;
}
}
private void setDialogTitle(String title) {
TextView view = (TextView) findViewById(R.id.dialog_title);
view.setText(title);
}
private void setDialogMessage(String message) {
TextView view = (TextView) findViewById(R.id.dialog_message);
view.setText(message);
}
private void loadButtons() {
Button positiveButton = (Button) findViewById(R.id.dialog_positive_button);
Button negativeButton = (Button) findViewById(R.id.dialog_negative_button);
Button neutralButton = (Button) findViewById(R.id.dialog_neutral_button);
positiveButton.setVisibility(View.GONE);
negativeButton.setVisibility(View.GONE);
neutralButton.setVisibility(View.GONE);
for (Map.Entry<DialogEventListener.ButtonType, String> entry: Builder.buttons.entrySet()) {
if (entry.getKey() == DialogEventListener.ButtonType.POSITIVE_BUTTON) {
positiveButton.setVisibility(View.VISIBLE);
positiveButton.setText(entry.getValue());
}
else if (entry.getKey() == DialogEventListener.ButtonType.NEGATIVE_BUTTON) {
negativeButton.setVisibility(View.VISIBLE);
negativeButton.setText(entry.getValue());
}
else if (entry.getKey() == DialogEventListener.ButtonType.NEUTRAL_BUTTON) {
neutralButton.setVisibility(View.VISIBLE);
negativeButton.setText(entry.getValue());
}
}
}
#Override
public void onBackPressed() {
//
}
public static final class Builder {
private static DialogEventListener callable;
private static DialogEventListener.DialogType type;
private static String title;
private static String message;
private Context context;
private static Map<DialogEventListener.ButtonType, String> buttons;
public Builder(Context context, DialogEventListener callable,
DialogEventListener.DialogType dialogType, String title, String message) {
Builder.callable = callable;
Builder.type = dialogType;
Builder.title = title;
Builder.message = message;
this.context = context;
Builder.buttons = new HashMap<DialogEventListener.ButtonType, String>();
}
public Intent build() {
return new Intent(this.context, DialogFactory.class);
}
public void addButton(DialogEventListener.ButtonType buttonType, String label) {
Builder.buttons.put(buttonType, label);
}
}
}
After looking at your code, I see multiple problems. To start with, the constructor in the Builder class is use less. And accessing all the attributes of the builder class with class name (declaring them as static) will give you uninitialized references and will result into null pointer exception.
I don't completely understand the purpose of your Builder class here, but if possible try to create a separate class that deals with the creation and initialization. After that create an instance of Builder class inside activity class, use constructor to inject dependencies, and try to use functions inside builder class to perform further operations.
I would like to know just out of curiosity if there are any convenient ways of pulling data out of an async task created inside a class, and then modifying the data in another class (Without extending classes)
I have a way to do it, but it involves making methods static along with the Async task itself
for example, here I'm just making a string "text" in the Async task
public class Main extends Activity{
//Context ctx;
static class MyAsyncTask extends AsyncTask<Void,String,String>{
static String result;
private static Context context;
public MyAsyncTask(Context m)
{
this.context = m;
}
#Override
protected String doInBackground(Void... noArgs) {
result = "text";
return result;
}
protected void onPostExecute(String result)
{
super.onPostExecute(result);
}
public static String getStr()
{
return result;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText et = (EditText)findViewById(R.id.editText1);
Button btn = (Button)findViewById(R.id.button1);
MyAsyncTask task = new MyAsyncTask(this);
task.execute();
final Test t = new Test();
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v)
{
et.append(t.modifiedString());
}
});
}
}
and in a new class I make a simple String method to modify that data from the async task
public class Test{
public String modifiedString()
{
// Main main = null;
// MyAsyncTask task = new MyAsyncTask(main.ctx);
// task.execute();
String s = (String)Main.MyAsyncTask.getStr();
return "modified " + s + "\n";
}
}
I'm wondering, is there a way I can do this without having to make the async task static? Perhaps with sharing contexts or something?
by the way I'm not doing this to solve any particular problem, I'm only doing it out of curiosity
Just create a singleton
public class Main extends Activity{
public static Main instance;
public static String thestring;
public class MyAsyncTask extends AsyncTask<Void,String,String>{
static final String result = "text";
Context context;
public MyAsyncTask(Context m)
{
this.context = m;
}
#Override
protected String doInBackground(Void... noArgs) {
return result;
}
protected void onPostExecute(String result)
{
super.onPostExecute(result);
}
public String getStr()
{
return result;
}
};
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
final EditText et = (EditText)findViewById(R.id.editText1);
Button btn = (Button)findViewById(R.id.button1);
MyAsyncTask task = new MyAsyncTask(this);
task.execute();
thestring = task.getStr();
instance = this;
final Test t = new Test();
btn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v)
{
et.append(t.modifiedString());
}
});
}
public String pulledFromAsyncTask()
{
return thestring;
}
public static Main getInstance(){
return instance;
}
}
and then in the another class
public class Test{
public String modifiedString()
{
Main main = Main.getInstance();
//so with main.something.. you can call the methods you want
//a good solution is to make a singleton class only for MyAsyncTask setting the
//functions get/set so you can take the values from other classes
return "modified " + main.pulledFromAsyncTask() + "\n";
}
}
Reference to a Context in a static way is generally bad idea, it can cause memory leaks
Why don't you simply pass MyAsyncTask object to Test and then do whatever modifications you want, i.e. non-static fashion?
When it comes to testable code static/ singleton is a tough choice.
Depending upon your requirement on the state of data you can however start with an Observer pattern or producer-consumer pattern.
Check out Event bus library for probably an out of the box solution for this use case