I am getting the data from JSON using volley, there is 3 Fragment in Activity which I have to populate these fragments with the data that I have received from JSON in MainActivity. I would like to get data from JSON once and use it in Activity and in all those 3 Fragment. I put the received data in a List like this:
List<Display_Activity_Model> videoDetailList;
and I send this list from activity to other fragments using an Interface like:
In activity I have this method:
#Override
public List<Display_Activity_Model> getDataList() {
return videoDetailList;
}
In one of fragments I have:
public interface GetDataInterface {
List<Display_Activity_Model> getDataList();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
sGetDataInterface= (GetDataInterface) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + "must implement GetDataInterface Interface");
}
}
#Override
public void onResume() {
super.onResume();
if(sGetDataInterface != null){
dataListFromDispAct = sGetDataInterface.getDataList();
}
}
When I debug the code, I see that the method getDataList in MainActivity is called before the method for fetching json with volley. So all the time I receive empty list.
My question is that: What is the best way to fetch data with volley once and use it in other fragments?
Update1:
#Keita Junichiro:
I defined "update" method in fragment like:
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.video_info,container,false);
initComponents(view);
if (dataListFromDispAct != null) {
txt_view_count.setText(dataListFromDispAct.get(0).getView_count());
}
return view;
}
public void update(List<Display_Activity_Model> videoDetailList){
dataListFromDispAct=videoDetailList;
}
and I am calling "update" method in activity after json loaded in "getItemMenuGson" method:
getItemMenuGson(new CallBack() {
#Override
public void onSuccess(List<Display_Activity_Model> itemMenuGsonList) {
videoDetailList=itemMenuGsonList;
new Video_Info().update(videoDetailList);
}
#Override
public void onFail(String msg) {
}
});
The problem is method "onCreateView" in fragment is called before update method. When I declare update method static it is also the same and variable "dataListFromDispAct" is null all the time.
Update2:
Update 3:
#Piyush
I implemented your respond, but it is not working in this problem. I am getting empty list because method onCreateView executing earlier than getItemMenuGson method in Activity. I declared :
SharedApplication mapp = SharedApplication.getInstance();
ArrayList<String> myList = mapp.getArrayListData();
in onActivityCreated in fragment but it is also running before getItemMenuGson method in Activity. The order in which methods are calling:
what should I do? How can I call getItemMenuGson method in Activity to fetch JSON and then load fragment layout to populate those loaded data to the fragment?
Solution: The key for the problem was Static method which runs first:
Define below codes in Fragment:
public class Video_Info extends Fragment {
static TextView txt_view_count;
List<Display_Activity_Model> dataListFromDispAct;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
View view=inflater.inflate(R.layout.video_info,container,false);
txt_view_count= (TextView) view.findViewById(R.id.txt_view_count);
return view;
}
public static void update(List<Display_Activity_Model> videoDetailList)
{
txt_view_count.setText(videoDetailList.get(0).getView_count());
}
}
In Activity after fetching data from JSON to list call update method:
getItemMenuGson(new CallBack() {
#Override
public void onSuccess(List<Display_Activity_Model> itemMenuGsonList) {
videoDetailList = itemMenuGsonList;
//////////////////
Video_Info.update(videoDetailList);
///////////////////
Uri vidUri = Uri.parse(itemMenuGsonList.get(0).getMedia().getURL());
vidView.setVideoURI(vidUri);
}
In order to send data from activity to fragment after call API, you can create a method update() in fragment to receive data and call fragment.update() in activity when loadData finish().
new Response.Listener<JSONObject>() {
#Override
public void onResponse(JSONObject response) {
//Parse resonse to *videoDetailList*
fragment.update(videoDetailList);
}
}
First make one singletone application class
public class SharedApplication extends Application{
ArrayList<String> arraylist;
public static SharedApplication instance=null;
#Override
public void onCreate() {
// TODO Auto-generated method stub
super.onCreate();
}
public static SharedApplication getInstance()
{
if(instance==null)
{
instance=new SharedApplication();
}
return instance;
}
public void setArrayListData(ArrayList<String> setData)
{
arraylist=setData;
}
public ArrayList<String> getArrayListData()
{
return arraylist;
}
}
Now in your activity initialize your application class using
SharedApplication mApp; // declare global
initialize in onCreate() method
mApp = SharedApplication.getInstance();
Now after parse your data from server set data in arraylist
mApp.setArrayListData(yourArrayList);
Now whenever you want to get data any of fragment you should same initialize your application class in each fragment in which u want to get arraylist.
Like,
SharedApplication mapp = SharedApplication.getInstance();
ArrayList<String> myList = mapp.getArrayListData();
Note : You must add SharedApplication class in your manifest to application tag
Make an interface with a method onDownloadComplete.
public interface DelegateClass {
public static onDownloadComplete(String jsonData);
}
Now fetch data with Volley in Activity and call this method. Implement this interface in all your fragments and override onDownloadComplete. This method will be called when your Activity finishes fetching data. Like
public class YourFragment implements DelegateClass {
...
...
#Override
public static onDownloadComplete(String jsonData){
//do what you want with data
}
...
...
}
Hope it helps
Related
I have a fragment (FragmentSearchResults) that contains results retrieved from a database, in which there is a button "filters". When the user taps on such a button, a class (FiltersDialog) extending a BottomSheetDialogFragment is instantiated, so that the user can set his filters. When the user closes the FiltersDialog activity, the values are passed from FiltersDialog to FragmentSearchResults.
public class FragmentSearchResults extends Fragment implements FiltersDialog.FilterListener {
/* code */
ImageButton btnFilter = myFragment.findViewById(R.id.btn_filters);
btnFilter.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
showFilters();
}
});
}
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
mContext = context;
}
#Override
public void onFiltersSet(Map filters) {
// apply filters selected by user
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
}
public class FiltersDialog extends BottomSheetDialogFragment {
private FilterListener mListener;
private Map<String, Object> mFilters;
public FiltersDialog() {
}
#Nullable
#Override
public View onCreateView(#NonNull LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.layout_filters_dialog, container, false);
TextView txtSave = v.findViewById(R.id.txt_save_filters);
mTxtSave.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
mListener.onFiltersSet(mFilters);
}
});
return v;
}
public interface FilterListener {
void onFiltersSet(Map filters);
}
#Override
public void onAttach(#NotNull Context context) {
super.onAttach(context);
if (context instanceof FilterListener) {
mListener = (FilterListener) context;
}
else {
// Here's the error, as the activity Home.java containing FragmentSearchResults
// does not implement FilterListener, FragmentSearchResults does
throw new RuntimeException(context.toString() + " must implement FilterListener");
}
}
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
}
The problem is that FilterListener needs to be implemented in FragmentSearchResults, but I am passing the activity Home context.
How can I implement FilterListener in the fragment?
Why don't you create method inside your FiltersDialog, like
public void setFiltersListener(FiltersDialog.FilterListener listener) {
mListener = listener;
}
and simply call it after you instantiate the dialog.
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setFiltersListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
Then you can use the listener inside dialog. something like this
if (mListener != null) {
mListener.onFiltersSet(mFilters);
}
How can I setup listener to the dialog?
parameter of onAttach in Fragment is FragmentHost(Activity). thus, it can't typecast to FilterListener.
I suggest a simple way to implement FilterListener setter in FragmentDialog as below code.
... in FiltersDialog
public void setListener(FilterListener listener) {
mListener = listener;
}
...
... in FragmentSearchResults
private void showFilters() {
FiltersDialog filtersDialog = new FiltersDialog();
filtersDialog.setListener(this);
filtersDialog.show(((FragmentActivity) mContext).getSupportFragmentManager(), "argument");
}
...
//When FragmentSearchResults recreated, FiltersDialog must also need to be recreated.
A better approach will be to use LiveData, ViewModel in this case. Use Shared ViewModel Approach, An Activity Level ViewModel can be accessed via all the fragments lying in its environment.
Make an Activity Level ViewModel
Define a LiveData in ViewModel
When your "FragmentSearchResults" opens for the first time, start
observing it.
When You open "FiltersDialog" screen and click save button, Then post
to LiveData changes in the filter (You have activity context here,
You can fetch ActivityViewModel here, get LiveData from it, post
changes to this LiveData)
Now As "FragmentSearchResults" is already observing changes in the
LiveData, You will get callback here, make changes accordingly. This way your code will be completely decoupled. You will be escaped from
hustles of Interfaces.
I wanted to PASS A MODEL OBJECT from Fragment1 to Fragment2using interface (Both fragments in the same activity).
I received that object in a public class of Fragment2. Now I want to save the received object as a Global variable of Fragment2. So that I can use it other methods of the same Fragment2.
But... But...
when I try to use the global object in onCreateView(),
getting NULL POINTER EXCEPTION.
In Fragment1,
1. Interface declaration
interface CommunicatePricePlanCheckOutInterface {
void sendDataToCheckout( SinglePricePlanModel singleModel );
}
2. Object of Interface
CommunicatePricePlanCheckOutInterface communicationObject;
3. passing data using method of interface in, onCreateView()
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_price_plan,
container,false);
allSampleData = new ArrayList<>();
mPricePlanContinueButton.setOnClickListener(new View.OnClickListener()
{
#Override
public void onClick(View v) {
if (getActivity() != null) {
communicationObject.sendDataToCheckout(
allSampleData.get(0));
}
}
});
return view;
}
In activity,
4. implementing the interface
public class NavDrawerActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener,
Fragment1.CommunicatePricePlanCheckOutInterface
5. overriding interface method and in the method calling a public method of Fragment2.
#Override
public void sendDataToCheckout(SinglePricePlanModel pricePlanModel ) {
Fragment2 frag2 = new Fragment2();
frag2.receivePlanData(pricePlanModel, mUserBasicInfo);
}
In Fragment2,
6. Received 2 objects from Activity, I can use them in this method, but can not access those mSinglePricePlanModel, mUserBasicInfo objects outside this method, like onCreateView or onViewCreated. Getting Null Pointer Exception if I try to
access.
public void receivePlanData(SinglePricePlanModel mSinglePricePlanModel , UserBasicInfo mUserBasicInfo ){
this.mSinglePricePlanModel = mSinglePricePlanModel;
this.mUserBasicInfo = mUserBasicInfo;
}
Have you initialized your interface object? I don't see any initialization in your fragment, that could be causing the null pointer exception. Initialize it inside onAttach method in your fragment like this-
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof CommunicatePricePlanCheckOutInterface) {
communicationObject = (CommunicatePricePlanCheckOutInterface) context;
}
}
It should not give null pointer exception then. Refer to this link for more details.
My project use MVP architecture and RxJava to get data from a remote JSON Api.
I have a MainActivity, it has 2 roles. The first one is to be a fragment container, the second one is to get data from the JSON api and transmit it to my fragment (I only have one fragment for now but will have another one later using the same data).
For now, I'm getting the data in my MainActivity. I'm trying to get the data from my fragment by calling a method in my MainActivity (using an interface for decoupling).
The problem is the data in my fragment is always empty, I suppose it's because my activity inflate my fragment so fast that when my fragment calls my activity method to get the data this data is still empty since the request didn't receive the answer yet and this request is called asynchronously using RxJava.
So I want to wait for the data being loaded to open my fragment,or open my fragment and wait the data being loaded in the activity before get it (showing a visual progress to the user). The problem is not really how to do this but when and where. Thank you for your help.
I moved my loadData() method and the transaction to open my fragment several times in different positions in the lifecycle, nothing worked. For now everything is in in MainActivity.onStart() :
#Override
protected void onStart() {
super.onStart();
presenter.setView(this);
// Load data from JSON API
presenter.loadData(city, authToken);
// Load fragments
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.ll_container);
if (fragment == null) {
fragment = new PollutionLevelsFragment();
fm.beginTransaction()
.add(R.id.ll_container, fragment)
.commit();
}
}
The data is retrieve in the loadData() method of my presenter :
public class MainPresenter implements MainActivityMVP.Presenter {
final static String TAG = MainPresenter.class.getCanonicalName();
private MainActivityMVP.View view;
private MainActivityMVP.Model model;
private Subscription subscription = null;
public MainPresenter(MainActivityMVP.Model model) { this.model = model; }
#Override
public void loadData(String city, String authToken) {
subscription = model.result(city, authToken)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Observer<Aqicn>() {
#Override
public void onCompleted() {
Log.i(TAG,"completed");
}
#Override
public void onError(Throwable e) {
e.printStackTrace();
}
#Override
public void onNext(Aqicn aqicn) {
Data data = aqicn.getData();
Iaqi iaqi = data.getIaqi();
ViewModel viewModel = new ViewModel(data.getAqi(),
data.getDominentpol(),
iaqi.getCo().getV(),
iaqi.getH().getV(),
iaqi.getNo2().getV(),
iaqi.getO3().getV(),
iaqi.getP().getV(),
iaqi.getPm10().getV(),
iaqi.getPm25().getV(),
iaqi.getR().getV(),
iaqi.getSo2().getV(),
iaqi.getT().getV(),
iaqi.getW().getV());
Log.d(TAG,data.getCity().getName());
if (view != null) {
view.updateData(viewModel);
}
}
});
}
#Override
public void rxUnsubscribe() {
if (subscription != null) {
if (!subscription.isUnsubscribed()) {
subscription.unsubscribe();
}
}
}
#Override
public void setView(MainActivityMVP.View view) {
this.view = view;
}
}
When the response to the request is received the presenter call the updateData() method in MainActivity (see in my presenter code above). This is where I initialize the ArrayList pollutionLevels that is supposed to contain the data I try to get from my fragment :
#Override
public void updateData(ViewModel viewModel) {
this.pollutionData = viewModel;
pollutionLevels = viewModel.getAllPolluants();
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
}
}
This is the method in my MainActivity called from my fragment to get data :
#Override
public ArrayList<PollutionLevel> getPollutionLevels() {
return pollutionLevels;
}
In my fragment I try to get the data in onAttach() but it's always empty :
public interface PollutionLevelsListener{
ArrayList<PollutionLevel> getPollutionLevels();
}
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
pollutionLevelsListener = (PollutionLevelsListener) context;
ArrayList<PollutionLevel> levels = pollutionLevelsListener.getPollutionLevels();
for(PollutionLevel l:levels) {
Log.d(TAG,l.getName());
}
} catch (ClassCastException castException){
castException.printStackTrace();
}
}
EDIT : add ViewModel.getAllPolluants() method
This is the method in my ViewModel that returns the ArrayList :
public ArrayList<PollutionLevel> getAllPolluants() {
ArrayList<PollutionLevel> allLevels = new ArrayList();
allLevels.add(new PollutionLevel("Co",Double.toString(co)));
allLevels.add(new PollutionLevel("H",Double.toString(h)));
allLevels.add(new PollutionLevel("No2",Double.toString(no2)));
allLevels.add(new PollutionLevel("o3",Double.toString(o3)));
allLevels.add(new PollutionLevel("p",Double.toString(p)));
allLevels.add(new PollutionLevel("o3",Double.toString(o3)));
allLevels.add(new PollutionLevel("pm10",Integer.toString(pm10)));
allLevels.add(new PollutionLevel("pm25",Integer.toString(pm25)));
allLevels.add(new PollutionLevel("r",Double.toString(r)));
allLevels.add(new PollutionLevel("so2",Double.toString(so2)));
allLevels.add(new PollutionLevel("t",Double.toString(t)));
allLevels.add(new PollutionLevel("w",Double.toString(w)));
return allLevels;
}
EDIT : Add new modified MainActivity class and PollutionLevelListener interface, trying to apply #cricket_007 answer
public class MainActivity extends AppCompatActivity implements MainActivityMVP.View, PollutionLevelsListener {
final static String TAG = MainActivity.class.getCanonicalName();
#BindString(R.string.city)
String city;
#BindString(R.string.aqicn_token)
String authToken;
#Inject
MainActivityMVP.Presenter presenter;
ArrayList<PollutionLevel> pollutionLevels;
PollutionLevelsListener pollutionListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
((App) getApplication()).getComponent().injectPollutionLevels(this);
}
#Override
public void updateData(ViewModel viewModel) {
pollutionLevels = viewModel.getAllPolluants();
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
}
//===== NullPointerException
pollutionListener.onPollutionLevelsLoaded(pollutionLevels);
}
#Override
protected void onStart() {
super.onStart();
presenter.setView(this);
presenter.loadData(city, authToken);
}
#Override
public void onPollutionLevelsLoaded(List<PollutionLevel> levels) {
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
};
// Load fragments
FragmentManager fm = getSupportFragmentManager();
Fragment fragment = fm.findFragmentById(R.id.ll_container);
if (fragment == null) {
fragment = new PollutionLevelsFragment();
fm.beginTransaction()
.add(R.id.ll_container, fragment)
.commit();
}
}
#Override
protected void onDestroy() {
super.onDestroy();
presenter.rxUnsubscribe();
}
}
Interface
public interface PollutionLevelsListener {
void onPollutionLevelsLoaded(List<PollutionLevel> levels);
}
#################### EDIT ########################
After a lot of doubt with what solution to adopt I follow the answer and recommendations of #yosriz. This is the code I ended with. Be aware that I still need to implement a cache management feature as for now the JSON resquest is made for both fragment.
As a result I have a common repository used by my both fragment. The MainActivity became only a fragment container, it doesn't get any data. it doesn't even have a MVP structure since I think It's now useless.
My both fragment (so my both features) get their data from PollutionLevelRepository :
public interface Repository {
Observable<Aqicn> getPollutionLevelsFromNetwork(String city, String authToken);
Observable<Aqicn> getPollutionLevels(String city, String authToken);
}
public class PollutionLevelsRepository implements Repository {
private PollutionApiService pollutionApiService;
private static Observable<Aqicn> pollutionData = null;
public PollutionLevelsRepository(PollutionApiService pollutionApiService) {
this.pollutionApiService = pollutionApiService;
}
#Override
public Observable<Aqicn> getPollutionLevelsFromNetwork(String city, String authToken) {
pollutionData = pollutionApiService.getPollutionObservable(city, authToken);
return pollutionData;
}
#Override
public Observable<Aqicn> getPollutionLevels(String city, String authToken) {
return getPollutionLevelsFromNetwork(city, authToken);
}
}
The Model of my first fragment (Donut feature) :
public class DonutModel implements DonutFragmentMVP.Model {
final static String TAG = DonutModel.class.getSimpleName();
private Repository repository;
public DonutModel(Repository repository) {
this.repository = repository;
}
#Override
public Observable<Aqicn> getPollutionLevels(String city, String authToken) {
Observable<Aqicn> aqicnObservable = repository.getPollutionLevels(city, authToken);
return aqicnObservable;
}
}
The Model of my second fragment (Pollution level feature) :
public class PollutionLevelsModel implements PollutionLevelsFragmentMVP.Model {
private Repository repository;
public PollutionLevelsModel(Repository repository) {
this.repository = repository;
}
#Override
public Observable<Aqicn> result(String city, String authToken) {
Observable<Aqicn> aqicnObservable = repository.getPollutionLevels(city, authToken);
return aqicnObservable;
}
}
Well, you probably have timing issue, the model.result is async IO operation that will update data on activity in async fashion when it will finish, while your fragment call to get the data is happening as soon as the fragment attached the activity (which is still async as you call fragment commit() and not commitNow()) but if you compare it to the probably network call of model.result it will be probably always faster.
Actually I think your approach is wrong, when you're using reactive fashion with Rx you should push the data, here at the end, you're pulling it at the fragment side from the Activity, while you don't know if this data is already available.
The data that is loaded from the presenter should update immediately the fragment, meaning either your Activity.updateData() will update the fragment, or more correct approach to my opinion is that the presenter will be tied to the fragment itself as this is the actual View it's updating, so the view.UpdateData() at the presenter will notify the fragment directly.
Did you tried to make an method inside the fragment and you can hit it once updateData(ViewModel viewModel) called ?
for example (try to add this method in you fragment):
public class YourFragmentName extends Fragment {
public YourFragmentName(StepsHandler stepsHandler){
this.stepsHandler = stepsHandler;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_supplier_registrtion_first, container, false);
return rootView;
}
public void dataLoaded() {
// Do what you need after data finish loading..
}
}
From your Activity :
public class MainActivity extends Activity implements StepsHandler {
YourFragmentName fragmentName;
//onCreate ()
fragmentName = new YourFragmentName(this);
#Override
public void updateData(ViewModel viewModel) {
this.pollutionData = viewModel;
pollutionLevels = viewModel.getAllPolluants();
fragmentName.dataLoaded();
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
}
}
}
I'm trying to get the data from my fragment by calling a method in my MainActivity
It seems your interface is only returning the field, which could very possibly be before the request has finished. Which you seem to understand...
didn't receive the answer yet and this request is called asynchronously using RxJava
I wouldn't suggest you wait, and instead do
open my fragment and wait the data being loaded in the activity before get it (showing a visual progress to the user).
However you want to implement that, you can try a new ProgressDialog() and show / hide that.
Your issue is that onAttach gets immediately called and the request is still going on indefinitely.
You need to "subscribe" for that data from the Fragment.
A "listener" is not typically written to implement a "getter", so let's rewrite that
public interface PollutionLevelsListener {
void onPollutionLevelsLoaded(List<PollutionLevel> levels);
}
Then, you can use that instead to start your Fragment rather than immediately when the Activity starts
// The Activity
class ... implements PollutionLevelsListener {
#Override
public void onPollutionLevelsLoaded(List<PollutionLevel> levels) {
for(PollutionLevel p : pollutionLevels) {
Log.d(TAG,p.getName());
};
// Moved this section here
// Load fragments
FragmentTransaction ft = getSupportFragmentManager().beginTransaction();
Fragment fragment = fm.findFragmentById(R.id.ll_container);
if (fragment == null) {
fragment = new PollutionLevelsFragment();
// If your object is Parcelable
/*
* Bundle args = new Bundle();
* args.putParcelableArrayList(levels);
* fragment.setArguments(args);
*/
ft.add(R.id.ll_container, fragment).commit();
}
}
And now that you have that method,
the presenter call the updateData() method in MainActivity
Well, there's where the list comes from, so just pass it to that new method where the Fragment is then loaded
#Override
public void updateData(ViewModel viewModel) {
this.pollutionData = viewModel;
if (pollutionLevels == null) {
pollutionsLevels = new ArrayList<>();
}
pollutionLevels.clear();
pollutionLevels.addAll(viewModel.getAllPolluants());
this.onPollutionLevelsLoaded(pollutionsLevels);
}
Let's say I have MainActivity where are few Fragments in ViewPager. I want to pass data from another Activity to one of these fragments. I'm doing this by BroadcastReceiver.
public class MyFragment extends Fragment {
private MyFragmentReceiver mReceiver;
public MyFragment() {
super();
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mReceiver = new MyFragmentReceiver();
getActivity().registerReceiver(mReceiver, new IntentFilter("fragmentUpdater"));
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_my, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
// My code here
}
public class MyFragmentReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//My methods
}
}
#Override
public void onDestroy() {
super.onDestroy();
if (mReceiver != null)
getActivity().unregisterReceiver(mReceiver);
}
}
So in my AnotherActivity I'm doing something like this:
Intent data = new Intent("fragmentUpdater");
MyApplication.getInstance().getMainActivity().sendBroadcast(data);
Where MyApplication is singleton which contains MainActivity.
I noticed that BroadcastReceiver is putting something into logs, and I am wondering is that the best way to do it.
Are there better ways to pass data from another activity to specific Fragment or call methods in that Fragment?
Do I have to include something in AndroidManifest.xml related to BroadcastReceiver?
One alternative is using an interface for communicating between your activity and fragments. Example:
Interface
public interface MyInterface {
void setSomeValue(int someValue);
int getSomeValue();
}
Activity
public class MyActivity extends Activity implements MyInterface {
private int someValue;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do the usual stuff
}
// implement from MyInterface
#Override
public void setSomeValue(int someValue) {
this.someValue = someValue;
}
// implement from MyInterface
#Override
public int getSomeValue() {
return someValue;
}
}
Fragment
public class MyFragment extends Fragment {
private MyInterface mi;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mi = (MyInterface) context;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mi.setSomeValue(20);
int someValue = mi.getSomeValue();
}
}
You can use the interface to communicate between one or more activities, multiple fragments, views, tasks, services, etc etc etc. If you were to go this route, I would create a base activity which implements MyInterface and its methods, and have all other activities extend the base activity. I would even create a base fragment which calls onAttach(), and have all my other fragments extend this base fragment (so that I don't need to call onAttach() in every fragment).
UPDATE...
A base fragment would simply look like this:
public class BaseFragment extends Fragment {
public MyInterface mi;
#Override
public void onAttach(Context context) {
super.onAttach(context);
mi = (MyInterface) context;
}
}
Now, MyFragment would just extend BaseFragment...
public class MyFragment extends BaseFragment {
...
}
There's no need now to attach or even declare MyInterface in any fragment extending BaseFragment, the base fragment already has a public instance of it. You just set/get/etc via your interface without any additional fuss:
mi.setSomeValue(20);
I would use LocalBroadcastManager instead, it gives you the following advantages :
You know that the data you are broadcasting won't leave your app, so
don't need to worry about leaking private data.
It is not possible for other applications to send these broadcasts
to your app, so you don't need to worry about having security holes
they can exploit.
It is more efficient than sending a global broadcast through the system.
This is directly from the official docs
You may pass the data using Extras.
Intent data = new Intent("fragmentUpdater");
data.putExtra("STRING_YOU_NEED", strName);
and you can get the data inside onReceive function by :
String data_needed_here= extras.getString("STRING_YOU_NEED");
Not sure what's wrong with my observer pattern:
I have defined my interface as:
public interface OnBackFilterPressListener {
public ArrayList<FoodType> filterFoodType ();
}
When I press the back button, I want the listener to be activated - all this code is in my activity class:
back.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
onBackFilterPressListener.filterFoodType();
}
}
In my adapter class, I have the following code:
public class RandomRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements SearchActivity.OnBackFilterPressListener {
#Override
public ArrayList<FoodType> filterFoodType() {
return foodTypes;
}
}
When I press the back button in my activity, I just want the array of data in my adapter to be returned in my activity class.
But I'm getting this error:
java.lang.NullPointerException: Attempt to invoke interface method
'java.util.ArrayList
com.example.simon..SearchActivity$OnBackFilterPressListener.filterFoodType()'
on a null object reference
foodType definitely exists - the adapter loads the data into activity and I can see it being displayed on the android emulator. I get this error when I click the back button.
I got it to work by following this thread and being a little creative:
How to create our own Listener interface in android?
I wrote a custom object to prevent the null object reference as to use the interface, I would first have to create the object:
public class BackFilterEvent implements Serializable {
private OnEventListener mOnEventListener;
public void setOnEventListener(OnEventListener listener) {
mOnEventListener = listener;
}
public ArrayList<FoodType> doEvent() {
if (mOnEventListener != null) {
return mOnEventListener.onEvent();
}
return null;
}
public interface OnEventListener {
ArrayList<FoodType> onEvent();
}
}
Then in my Activity class, I created the object:
BackFilterEvent backFilterEvent = new BackFilterEvent();
And I pass it as far as it needs to go in my code. In my case, I had an activity that feed a fragment with an recyclerview adapter within the fragment. I passed this object until it got to the recyclerview adapter.
Passing object from Activity to Fragment:
FilterSearchFragment filterSearchFragment = new FilterSearchFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("listener", backFilterEvent);
filterSearchFragment.setArguments(bundle);
getSupportFragmentManager().beginTransaction()
.replace(R.id.fragment_container, filterSearchFragment)
.commit();
Passing from Fragment to Adapter:
Bundle bundle = getArguments();
BackFilterEvent backFilterEvent = (BackFilterEvent) bundle.getSerializable("listener");
RandomRecyclerViewAdapter randomRecyclerViewAdapter = new RandomRecyclerViewAdapter(getContext(), backFilterEvent);
In my adapter constructor, I set it to do the work once I call it from the activity class:
this.backFilterEvent = backFilterEvent;
backFilterEvent.setOnEventListener(new BackFilterEvent.OnEventListener() {
#Override
public ArrayList<FoodType> onEvent() {
return foodTypes;
}
});
And in my back button in my activity class, I call the listener, which will trigger the work in the adapter class:
back.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
ArrayList<FoodType> foodTypes = backFilterEvent.doEvent(); }