Passing data between two fragments through async task - java

I have just started out with Java development(android app) and I stumbled upon a problem I don't know how to solve.
So I have two fragments:
1) Fragment with barcode scanner
2) Fragment with just a simple textview
The app should be able to scan barcode, get API response based on the scan result, deserialize it into a Java object and then show value of one variable in the textview located in the second fragment.
I have already implemented the barcode scanner and class to get data from API and turn it into a Java object. The problem is that I can't find a way to send the barcode result to the class that handles the API data retrieval and also how to send the object to the second fragment.
Can someone please direct me in the right way on how to implement it correctly?
1)Barcode fragment
public class BarcodeFragment extends Fragment {
private CodeScanner mCodeScanner;
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container,
#Nullable Bundle savedInstanceState) {
final Activity activity = getActivity();
View root = inflater.inflate(R.layout.barcode_fragment, container, false);
CodeScannerView scannerView = root.findViewById(R.id.scanner_view);
mCodeScanner = new CodeScanner(activity, scannerView);
mCodeScanner.setDecodeCallback(new DecodeCallback() {
#Override
public void onDecoded(#NonNull final Result result) {
activity.runOnUiThread(new Runnable() {
#Override
public void run() {
Toast.makeText(activity.getApplicationContext(), result.getText(), Toast.LENGTH_SHORT).show();
}
});
}
});
scannerView.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
mCodeScanner.startPreview();
}
});
return root;
}
#Override
public void onResume() {
super.onResume();
mCodeScanner.startPreview();
}
#Override
public void onPause() {
mCodeScanner.releaseResources();
super.onPause();
}
}
2) Class to get data from API and turn it into JAVA object
public class RetrieveFeedTask extends AsyncTask<Void, Void, String> {
Product productFromDatabase;
String resultString;
public RetrieveFeedTask(String barcodeResult){
resultString = barcodeResult;
}
protected void onPreExecute() {
}
protected String doInBackground(Void... urls) {
try {
URL url = new URL("https://api.appery.io/rest/1/apiexpress/api/example/Products?apiKey=12345678&Barcode=" + resultString);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
try {
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
StringBuilder stringBuilder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append("\n");
}
bufferedReader.close();
return stringBuilder.toString();
}
finally{
urlConnection.disconnect();
}
}
catch(Exception e) {
Log.e("ERROR", e.getMessage(), e);
return null;
}
}
protected void onPostExecute(String response) {
if(response == null) {
response = "THERE WAS AN ERROR";
}
Log.i("INFO", response);
deSerializeProduct(response);
}
public void deSerializeProduct(String response){
response = response.substring(1,response.length() - 3);
StringBuilder stringBuilder = new StringBuilder(response);
stringBuilder.append(",\"productId\":\"23323123sdasd\"}"); // for testing
String responseToDeSerialize = stringBuilder.toString();
ObjectMapper mapper = new ObjectMapper();
try {
productFromDatabase = mapper.readValue(responseToDeSerialize, Product.class);
} catch (IOException e) {
e.printStackTrace();
}
}
}
3) Cart fragment class where to name of the object should appear in the textview
public class CartFragment extends Fragment {
static TextView showReceivedData;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup parent, Bundle savedInstanceState) {
// Defines the xml file for the fragment
View view = inflater.inflate(R.layout.cart_fragment, parent, false);
showReceivedData = (TextView) view.findViewById(R.id.resultCode);
return view;
}
}

The asynctask shouldn't outlive the lifecycle of the activity or fragment that starts it. I'm assuming you probably will display some sort of loading status while the network request happens. Here are some options:
If the two fragments are in the same activity, you could pass the result of the scan to the activity, kick off the network request, swap fragments, and send the request result to the second fragment.
The scanner fragment can get the barcode data, kick off the request, show the loading state, and when the result returns, package in the bundle, which the second fragment can read.
Invert the previous model, if it fits your app better, and send just the barcode result in the bundle, and have the second fragment kick off the request while displaying the loading status.
The exact choice will depend on the flow and structure of your app. Additionally, you may want to look into using another multithreading option instead of asynctask, as it has been deprecated and Google is trying to move developers away from it. Some alternatives are the Java concurrency library, RxJava, or if you are willing to use Kotlin in your project, Kotlin coroutines.

Use Bundle to add pass data between Activity and fragments
try This code to Pass data between two fragments
Bundle bundle=new Bundle();
bundle.putString("scanner_data","myData");
Fragment fragment=new HomeFragment();
fragment.setArguments(bundle);
FragmentTransaction ft = getActivity().getSupportFragmentManager().beginTransaction();
ft.add(R.id.framContainer, fragment, "TAg");
ft.commit();
How to get data
Bundle bundle=getArguments();

Related

Passing data from Activity to Fragment using AsyncTask - Android

I'm trying to pass an ArrayList from an AsyncTask in the MainActivity to a fragment, but I'm getting a NullPointerException for invoking
CategoryAdapter.getItemCount() even if I'm passing the array after the BroadCastReceiver Invoke.
What Am I doing wrong?
MainActivity
class GetBooksAsync extends AsyncTask<Void, Void, Void> {
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
#Override
protected Void doInBackground(Void... voids) {
for (ECategories category : ECategories.values()) {
try {
categories.add(new Category(category.toString(), apiClient.getBooks(category)));
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Intent intent = new Intent("com.android.mainapp");
intent.putExtra("categories", categories);
manager.sendBroadcast(intent);
replaceFragment(new HomeFragment());
}
}
HomeFragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
initBroadCastReceiver();
categoryAdapter = new CategoryAdapter(categories,getContext());
View view = inflater.inflate(R.layout.fragment_home, container, false);
recyclerView = view.findViewById(R.id.parent_rv);
recyclerView.setAdapter(categoryAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
categoryAdapter.notifyDataSetChanged();
return view;
}
private void initBroadCastReceiver() {
manager = LocalBroadcastManager.getInstance(getContext());
MyBroadCastReceiver receiver = new MyBroadCastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.android.mainapp");
manager.registerReceiver(receiver,filter);
}
class MyBroadCastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//get the categories from the intent
categories = new ArrayList<Category>();
categories = (ArrayList<Category>) intent.getSerializableExtra("categories");
}
}
i've also tried attaching the recyclerView from the OnReceive Method, but it's not getting attached.
Thank you in advance!
I think there are several problems with your code:
Your task is running in a different thread than the UIThread (which schedules the task and processes the result). That means it most probably runs on a different processor/core. Processed values (such as your collection) are cached in a processor and somewhen after execution the data is written back to RAM. But that might happen after the onPostExecute method is called, which takes the collection to another processor cache as well. But when this is done before the collection is returned to the RAM from the task, it's still empty. That's called a race condition.
Now there are several ways to solve that. The simplest one is to use Collections.synchronizedList(categories)
This prevents the processor from caching list values and always return it to the RAM (or using L3 cache which is shared between all processors/cores).
I'm not sure what exactly you pass to the collection. Intents (and it's data) need to be serializable and what you add to your collection is probably not serializable.
Then I would use the AsyncTask parameters:
class GetBooksAsync extends AsyncTask<ECategories, Void, Collection<Category>> {
LocalBroadcastManager manager = LocalBroadcastManager.getInstance(getApplicationContext());
#Override
protected Void doInBackground(ECategories... eCategories) {
Collection<Category> categories = [whatever you want to use];
for (ECategories category : eCategories) {
try {
categories.add(new Category(category.toString(), apiClient.getBooks(category)));
} catch (IOException e) {
e.printStackTrace();
}
}
return categories;
}
#Override
protected void onPostExecute(Collection<Category> categories) {
super.onPostExecute(categories);
Intent intent = new Intent("com.android.mainapp");
intent.putExtra("categories", categories);
manager.sendBroadcast(intent);
replaceFragment(new HomeFragment());
}
}
And note that AsyncTask and LocalBroadcastManager are deprecated.
Is Category serialized?
You can use BroadcastReceiver as an internal class, and then update the data of Adpater when it receives the data, because the code runs very fast, and it is not necessary to register for monitoring, and it will be processed immediately.
I guess the way you pass the data from MainActivity to HomeFragment is incorrect.
WHAT YOU EXPECT
Call MainActivity#GetBooksAsync
Wait till onPostExecute has been called
HomeFragment is ready to receive the broadcast message, then update UI
Broadcast the message from MainActivity to the fragment
WHAT IS HAPPENING HERE
Call MainActivity#GetBooksAsync
Wait till onPostExecute has been called
Broadcast the message from MainActivity. There is no receiver to receive this message!
HomeFragment is ready to receive the broadcast message, then update UI
HOW SHALL YOU PASS THE DATA THEN?
There are several way.
Broadcast data between the UI component like the things you did. But you will need to beaware the life cycle of the components. That is, when you broadcast the data, the receiver must already init and the UI component is in active.
Build a singleton class to store the data. Your activity and fragment treats the singleton class as a common place for the data storage.
Use Intent and the extra property to pass the data IF the data size is small enough.
Use LiveData. I believe it is the most modern way recommended by the community. Though I am not sure how its work.
To verify the fact that it is an life cycle issue,
you can try to add a delay before you sending the broadcast message.
class GetBooksAsync extends AsyncTask<Void, Void, Void> {
...
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Intent intent = new Intent("com.android.mainapp");
intent.putExtra("categories", categories);
TimerTask task = new TimerTask() {
#Override
public void run() {
manager.sendBroadcast(intent);
}
};
Timer timer = new Timer();
timer.schedule(task, 5 * 1000); // Delay the broadcast after 5 seconds
replaceFragment(new HomeFragment());
}
Your Adapter should be written like this.
class CategoryAdapter extends RecyclerView.Adapter<CategoryAdapter.VHolder>{
private ArrayList<Category> list = new ArrayList<Category>();
public void setList(ArrayList<Category> list) {
this.list = list;
notifyDataSetChanged();
}
public CategoryAdapter(Context context) {
// Do not pass a list in the constructor, because the list may be empty
}
class VHolder extends RecyclerView.ViewHolder {
public VHolder(#NonNull View itemView) {
super(itemView);
}
}
......
}
Your fragment should have a global Adapter for BroadcastReceiver to update data
public class Test extends Fragment {
// Create a global Adapter for BroadcastReceiver to call and update data
private CategoryAdapter adapter;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
adapter = new CategoryAdapter(getContext());
initBroadCastReceiver();
View view = inflater.inflate(R.layout.fragment_home, container, false);
recyclerView = view.findViewById(R.id.parent_rv);
recyclerView.setAdapter(categoryAdapter);
recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
return view;
}
private void initBroadCastReceiver() {
manager = LocalBroadcastManager.getInstance(getContext());
MyBroadCastReceiver receiver = new MyBroadCastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.android.mainapp");
manager.registerReceiver(receiver,filter);
}
class MyBroadCastReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
//get the categories from the intent
ArrayList<Category> categories = (ArrayList<Category>) intent.getSerializableExtra("categories");
adapter.setList(categories);
}
}
}

JSON download to ListView [duplicate]

This question already has answers here:
How do I parse JSON in Android? [duplicate]
(3 answers)
Closed 6 years ago.
I found this code that I have modified to suit my needs. However I am facing a bit of an issue. It appears that the data is obtained from the remote host but cannot be parsed into adapter.
I have reviewed my entire code structure to ensure that everything is in place but I cant seem to find the problem. The ListView is inside of a Fragment that is part of a TabbedActivity.
This my code:
Fragment inside a Tabbed Activity
public class shops extends Fragment {
String url="http://link to remote webservice";
//FragmentManager fm;
//newInstance() method return reference to fragment
public static shops newInstance(){
shops fragment = new shops();
return fragment;
}
public shops() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
//fm = getFragmentManager();
View view = inflater.inflate(R.layout.fragment_shops, container, false);
final ListView listView = (ListView)view.findViewById(R.id.shops_info);
final Downloader d =new Downloader(getActivity(),url,listView);
d.execute();
//calls DialoFragment
FloatingActionButton fab = (FloatingActionButton) view.findViewById(R.id.fab_edset);
fab.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
DialogFragment newEdQua = new createNewEdQua();
newEdQua.show(getActivity().getFragmentManager(), "createNewEdQua");
}
});
// Inflate the layout for this fragment
return view;
}
}
Downloader(receives data and parses in the same class)
public class Downloader extends AsyncTask<Void,Integer,String> {
Context c;
String retredq_url;
ListView listView;
String data;
ArrayList<String> shopl=new ArrayList<String>();//its the ArrayList that we bind to ListView
ProgressDialog pd;
public Downloader(Context c, String retredq_url, ListView listView){
this.c=c;
this.retredq_url=retredq_url;
this.listView=listView;
}
//Before job starts
#Override
protected void onPreExecute(){
super.onPreExecute();
pd=new ProgressDialog(c);
pd.setTitle("Refreshing List");
pd.setMessage("Please Wait...");
pd.show();
}
#Override
protected String doInBackground(Void... params) {
data=downloadData();
return data;
}
#Override
protected void onPostExecute(String s){
super.onPostExecute(s);
pd.dismiss();
if (s !=null){
try{
JSONArray ja=new JSONArray(data);
//JSONObject jo=null;
shopl.clear();//we need to add the data to ArrayList, so clear list first to avoid duplicates
for (int i=0;i<ja.length();i++){
String shops=ja.getJSONObject(i).getString("Qualification")+ ja.getJSONObject(i).get("eq_end_date")+
ja.getJSONObject(i).get("eq_loc_shops");//retrieve the column name into a string
shopl.add(shops);
ArrayAdapter<String> adapter=new ArrayAdapter<String>(c,R.layout.list_item_shopl,shopl);
listView.setAdapter(adapter);
listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
#Override
public void onItemClick(AdapterView<?> adapterView, View view, int i, long l) {
Snackbar.make(view,shopl.get(i),Snackbar.LENGTH_LONG).show();
}
});
}
} catch (JSONException e) {
Log.e("Downloader", "Error", e);
}
/*
//call the Parser here to parse the JSON after we confirm string writer is not null
Parser p=new Parser(c,s,listView);
p.execute();*/
}else {
Toast.makeText(c,"Unable to download data", Toast.LENGTH_SHORT).show();
}
}
private String downloadData(){
//connect and get a stream
InputStream inputStream=null;
String line=null;
try{
URL url=new URL(retredq_url);
HttpURLConnection con=(HttpURLConnection) url.openConnection();
inputStream=new BufferedInputStream(con.getInputStream());
BufferedReader br=new BufferedReader(new InputStreamReader(inputStream));
StringBuffer sb=new StringBuffer();
if (br !=null){
while ((line=br.readLine()) !=null){
sb.append(line+"\n");
}
}else{return null;}
return sb.toString();
} catch (MalformlocRLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}finally {
if (inputStream !=null){
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
}
JSON output (Checked with ARC plugin on Chrome)
{"qualifications":[{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"}],"success":1}
A slight difference from what ADM sees (The success message comes first here)
{"success":1,"qualifications":[{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"},{"eq_edu_Institution":"Oracle","eq_start_Date":"1998-06-14","eq_end_date":"2005-08-23","Qualification":"Software Engineer"}]}
I defined the success message while struturing the array in php webservice
I intend use the Downloader class in several tabs for the same purpose(retrieve data from url parse and display in ListView). The urls and data are independent ot each other so I guess it should work...
Going over LogCat and reviewing JSON data obtained from server I was able to figure the problem. My previous code would have worked without any issues if the data was an Array with Objects in it. But I checked and the structure of the JSON was an Object with the Array inside.
What I had to do was get the Object with JSONObject then that Object retrieve the Array with JSONArray.. Like this:
JSONObject jsonObject=new JSONObject(data);
JSONArray jsonArray= jsonObject.getJSONArray("qualifications");
//now this Array has Objects needed
for (int i=0;i<jsonArray.length();i++){
String institution=jsonArray.getJSONObject(i).getString("Qualification");
edqua.add(institution);
}
//provide the ArrayAdapter<> needed
ArrayAdapter<String> adapter=new ArrayAdapter<String>(c,R.layout.list_item_edqua,edqua);
listView.setAdapter(adapter);
It should be in a try-catch block.. It is good to know the structure of the JSON that is expected. I was rather asking for an Array when Object was being offerd, hence the type mis-match error. I also realised that I will be unable to use the same class for different data sources, as the tables are completely. Any suggestions of how to use one class for different data urls will appreciated.
As the ArrayList<String> shows, the list item will be like
Qualification eq_end_date eq_loc_shops as a single string, so initialize the adapter like
adapter = new ArrayAdapter<>(context,android.R.layout.simple_list_item_1,shopl);
Most of your code can be reused but the part which starts from data generated by downloadData() to the JSONArrayObject that your listview needs.
So you can extract these code to an Interface(here called IPreParser)'s method (called JSONArrayObject arrayFromData(String)), like this:
public interface IPreParser{
JSONArrayObject arrayFromData(String data);
}
Your Downloader need hold a reference to IPreParser, and invoke its method in onPostExecute(). And you can initialize this reference by declaring doInBackground(IPreParser).
In anywhere you what download and parse your data, just implement IPreParser, and then execute your downloader with downloader.execute(yourImplementor);

Fragment Crashing when receiving data from an Activity

Good day all,
I have an issue where my activity is making a network call and when the network call is completed, it makes some changes in the activity using the data from the JSON object received from the call, it then passes the object down to the fragments in the same activity. These fragments are in a TabLayout.
I had this same issue which I asked here at this SO Question That sorted it out but I seem to be having the same issue, even after it worked for a little bit after not changing anything significant. I was just adding more fields I wanted to change?
The issue I have is that if I put a System.out.println() it prints out the correct data. The minute I want to set say a TextView with the data I receive in the Fragment the app Crashes with Nullpointer. When I debug it with the Debug in Android studio, the TextView I'm setting is always null for some reason.
Activity Code that does the initial Network call:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_listings);
ButterKnife.bind(this);
setSupportActionBar(toolbar);
handleIntent(getIntent());
}
private void handleIntent(Intent aIntent) {
if (aIntent != null) {
String tradeType = aIntent.getStringExtra("itemType");
String tradeId = aIntent.getStringExtra("itemId");
presenter = new ItemPresenterImpl(this, ItemBuyNowActivity.this);
presenter.doListingServiceCall(tradeId); // <------- This is the where I send the Trade Id so I can do the network call.
} else {
System.out.println("Intent is null in " + ItemBuyNowActivity.class.getSimpleName());
}
}
Interface between Activity and Presenter:
public interface ItemPresenter {
void doListingServiceCall(String itemId); //<------- Comes to this Interface
void doToolbarBackgroundImageCall(TradeItem aTradeItem);
}
Class the implements the Presenter:
#Override
public void doListingServiceCall(String aItemId) { // <------- This is where the network call starts
String homeURL = BobeApplication.getInstance().getWsURL() + mContext.getString(R.string.ws_url_item) + aItemId;
BobeJSONRequest jsObjRequest = new BobeJSONRequest(Request.Method.GET, homeURL, null, this, this);
VolleySingleton.getInstance().addToRequestQueue(jsObjRequest, "ListingRequest");
}
#Override
public void doToolbarBackgroundImageCall(TradeItem aTradeItem) {
ImageRequest request = new ImageRequest(aTradeItem.getItem().getImageUrl(),
new Response.Listener<Bitmap>() {
#Override
public void onResponse(Bitmap bitmap) {
Drawable drawable = new BitmapDrawable(mContext.getResources(), bitmap);
mItemView.loadBackgroundImage(drawable);
}
}, 0, 0, null,
new Response.ErrorListener() {
public void onErrorResponse(VolleyError error) {
mItemView.displayErrorMessage(VolleyErrorHelper.getErrorType(error, mContext) + " occurred downloading background image");
}
});
VolleySingleton.getInstance().addToRequestQueue(request, "ListItemToolbarBackgroundImageRequest");
}
#Override
public void onResponse(Object response) {
Gson gson = new Gson();
TradeItem tradeItem = gson.fromJson(response.toString(), TradeItem.class);
mItemView.populateListViews(tradeItem); // <------- This is the where I send the Object so the views in the activity can be manipulated
doToolbarBackgroundImageCall(tradeItem);
}
Method in the Activity that handles
#Override
public void populateListViews(TradeItem aTradeItem) {
mOverviewPresenter = new OverviewPresenterImpl(new OverviewListItemFragment(), aTradeItem);
OverviewListItemFragment.setData(aTradeItem); //<------- This is the where I send the Object to the fragment so i can manipulate the views in the fragment
}
class TabAdapter extends FragmentPagerAdapter {
public TabAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
Fragment fragment = null;
if (position == 0) {
fragment = new OverviewListItemFragment();
}
if (position == 1) {
fragment = new DescriptionListItemFragment();
}
if (position == 2) {
fragment = new ShippingListItemFragment();
}
if (position == 3) {
fragment = new PaymentListItemFragment();
}
return fragment;
}
#Override
public int getCount() {
return 4;
}
#Override
public CharSequence getPageTitle(int position) {
if (position == 0) {
return "Overview";
}
if (position == 1) {
return "Description";
}
if (position == 2) {
return "Shipping";
}
if (position == 3) {
return "Payment";
}
return null;
}
}
The Fragment that receives the data:
public class OverviewListItemFragment extends Fragment implements OverviewView {
private static TextView mOverViewHeading;
public OverviewListItemFragment() {
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.overview_list_item_fragment, container, false);
mOverViewHeading = (TextView) view.findViewById(R.id.frag_overview_heading_textview);
return view;
}
#Override
public void populateOverviewViews(final TradeItem aTradeItem) {
System.out.println("Overview Trade Object title is:" + aTradeItem.getItem().getTradeTitle()); // <------- This is print statement works 100% but when I try setting mOverViewHeading to the text in aTradeItem.getItem().getTradeTitle() I get a Null pointer Exception.
}
public static void setData(TradeItem aTradeItem) {
System.out.println("Overview Trade Object title is:" + aTradeItem.getItem().getTradeTitle()); // <------- This is print statement works 100% but when I try setting mOverViewHeading to the text in aTradeItem.getItem().getTradeTitle() I get a Null pointer Exception.
mOverViewHeading.setText(aTradeItem.getItem().getTradeTitle());// <------- This is where it crashes and mOverViewHeading is still null at this point.
}
}
EDIT: Sorry I forgot the LogCat:
02-05 17:08:21.554 30512-30512/com.example.app E/AndroidRuntime: FATAL EXCEPTION: main
java.lang.NullPointerException
at com.example.app.ui.fragments.OverviewListItemFragment.setData(OverviewListItemFragment.java:46)
at com.example.app.ui.activities.ItemBuyNowActivity.populateListViews(ItemBuyNowActivity.java:95)
at com.example.app.listing.ItemPresenterImpl.onResponse(ItemPresenterImpl.java:62)
at com.android.volley.toolbox.JsonRequest.deliverResponse(JsonRequest.java:65)
at com.android.volley.ExecutorDelivery$ResponseDeliveryRunnable.run(ExecutorDelivery.java:99)
at android.os.Handler.handleCallback(Handler.java:725)
at android.os.Handler.dispatchMessage(Handler.java:92)
at android.os.Looper.loop(Looper.java:137)
at android.app.ActivityThread.main(ActivityThread.java:5041)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:511)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
at dalvik.system.NativeStart.main(Native Method)
My thinking is that the view I'm trying to set isn't "Active" (if thats the right word) at the time it receives the data, because when I run the debugger with a break point at the method that receives the data in the Fragment, the mOverViewHeading TextView id is null, even though I have the findViewById in the onCreate, also tried placing it in the onCreateView() but both times failed. I also tried placing the findViewById in the same method that gets called when the response is successful but before I try setting the setText() on the TextView.
Thank you
OverviewListItemFragment I assume this is not your added fragment instance, but the class.
I suggest the following changes: remove static from setData and your TextView, leave it, if you really know how it works. I don't think it is necessary or recommendable.
private OverviewListItemFragment mFrag; //declare globally
mFrag = new OverviewListItemFragment();
//if you do not want to add it now, ignore the following line
getSupportFragmentManager().beginTransaction().add(R.id.yourContainer, mFrag, "mFrag").commit();
now call mFrag.setData everytime you want to set your data. Check if your mFrag is null, then reinitialize, and maybe re-add, or whatever you want to do.
Edit: Now that I know that you use a ViewPager, I suggest the following:
Do the above. I don't think it is recommendable to have static methods in this Context. You get an error because you are trying to reach a TextView in your Fragment. This was initialized in a ViewPager/PagerAdapter, and the PagerAdapter holds the reference to the used instance of your fragment.
You can access your used fragment through
Fragment mFragment = pagerAdapter.getFragment(0); //frag at position 0
with some casting, you will be able to find your (now NOT static) method:
((OverviewListItemFragment)pagerAdapter.getFragment(0)).setData(YOUR_DATA);
Please add some try/catch. check if your fragment is null, because it is possible that your fragment is recycled in the FragmentPagerAdapter, because it reached the offset. Another way to achieve this, would be to store your required data, and update it everytime your fragment gets visible as described here.
Edit 2: Obviously, You'll need some changed in your Adapter:
I would recommend creating an array containing your fragment in the constructor:
//global in your adapter:
private Fragment[] fragments;
public CustomPagerAdapter(FragmentManager fm) {
super(fm);
fragments = new GameFragment[4];
fragments[0] = new MyFragment();
fragments[1] = new SecondFragment();
....
}
public Fragment getItem(int position) {
return fragments[position];
}
public Fragment getFragment(int position) {
return fragments[position];
}

Android: Passing Objects Between Fragments

Before i start, i have look through question such as:
Passing data between fragments: screen overlap
How to pass values between Fragments
as well as Android docs:
http://developer.android.com/training/basics/fragments/communicating.html
as well as this article:
http://manishkpr.webheavens.com/android-passing-data-between-fragments/
Though all the cases mentioned above similar to what i have, it is not entirely identical. I followed a good tutorial here (Some portion of my code is based on this article):
http://www.androidhive.info/2013/10/android-tab-layout-with-swipeable-views-1/
I have the following files:
RegisterActivity.java
NonSwipeableViewPager.java
ScreenSliderAdapter.java
RegisterOneFragment.java
RegisterTwoFragment.java
And the following layouts:
activity_register.xml
fragment_register_one.xml
fragment_register_two.xml
What i am trying to achieve is passing an Serializable object from RegisterFragmentOne to RegisterFragmentTwo.
So far this is what i have done (some codes are omitted):
RegisterActivity.java
public class RegisterActivity extends FragmentActivity
implements RegisterOneFragment.OnEmailRegisteredListener{
public static NonSwipeableViewPager viewPager;
private ScreenSliderAdapter mAdapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_register);
// Initilization
mAdapter = new ScreenSliderAdapter(getSupportFragmentManager());
viewPager = (NonSwipeableViewPager) findViewById(R.id.pager);
viewPager.setAdapter(mAdapter);
}
public void onEmailRegistered(int position, Registration regData){
Bundle args = new Bundle();
args.putSerializable("regData", regData);
viewPager.setCurrentItem(position, true);
}
}
ScreenSliderAdapter.java
public class ScreenSliderAdapter extends FragmentPagerAdapter{
public ScreenSliderAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new RegisterOneFragment();
case 1:
return new RegisterTwoFragment();
case 2:
return new RegisterThreeFragment();
}
return null;
}
#Override
public int getCount() {
return 3;
}
}
NonSwipeableViewPager.java (extending ViewPager class, and overrides the following)
#Override
public boolean onInterceptTouchEvent(MotionEvent arg0) {
// Never allow swiping to switch between pages
return false;
}
#Override
public boolean onTouchEvent(MotionEvent event) {
// Never allow swiping to switch between pages
return false;
}
RegisterOneFragment.java
public class RegisterOneFragment extends Fragment {
OnEmailRegisteredListener mCallBack;
public interface OnEmailRegisteredListener {
/** Called by RegisterOneFragment when an email is registered */
public void onEmailRegistered(int position, Registration regData);
}
public void onAttach(Activity activity){
super.onAttach(activity);
// This makes sure that the container activity has implemented
// the callback interface. If not, it throws an exception.
try {
mCallBack = (OnEmailRegisteredListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnEmailRegisteredListener");
}
}
... And some to execute some HTTP request via separate thread...
}
What i am trying to accomplish is that when ever a user pressed a button on RegisterOneFragment, a data will be sent to a server (and returns some validation over JSON). If the returned data is valid, the the application should go to the next fragment which is RegistrationTwoFragment.
I am having some confusion as how to pass objects between fragments, since my Fragments is created using an Adapter. And that Adapter is then attached to my Activity.
Can anyone help me with this? Thx
Edit 1:
I tried to make a shortcut (unfortunately does not work) like so:
In RegisterActivity i created:
public Registration regData;
and in RegisterOneFragment:
/* PLACED ON POST EXECUTE */
((RegisterActivity)getActivity()).regData = regData;
Finally called it in RegisterTwoFragment
Registration regData;
regData = ((RegisterActivity) getActivity()).regData;
It throws a nullPointerExceptions
Edit 2
Just to be clear, RegisterActivty contains multiple fragments. And the only way user can navigate between fragment is by clicking a button. The Activity has no Tab bar.
It's easy to share objects via implementing Serializable to your custom Object. I wrote a tutorial about this here.
From Fragment One:
android.support.v4.app.FragmentTransaction ft =
getActivity().getSupportFragmentManager().beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
OfficeCategoryFragment frag = new OfficeCategoryFragment();
Bundle bundles = new Bundle();
Division aDivision = divisionList.get(position);
// ensure your object has not null
if (aDivision != null) {
bundles.putSerializable("aDivision", aDivision);
Log.e("aDivision", "is valid");
} else {
Log.e("aDivision", "is null");
}
frag.setArguments(bundles);
ft.replace(android.R.id.content, frag);
ft.addToBackStack(null);
ft.commit();
In Fragment two:
Bundle bundle = getArguments();
Division division= (Division) bundle.getSerializable("aDivision");
Log.e("division TEST", "" + division.getName());
I would normally have setters or methods similar to this in the containing activity.
So if I understand correctly, you want the user to access RegistrationOneFragment, then when completed, use this data, validate it, and if valid, pass it along to RegistrationTwoFragment and move the user to this Fragment.
Could you simply call validateJson(regData) in your onEmailRegistered method to handle the validation in your activity, if it succeeds, commit a transaction to RegistrationTwoFragment.
Then all you need are getters and setters in your activity or Fragment to say getRegistrationOneData() in the activity or setData(Registration args) in the fragment as your examples show above.
I don't know of any way to pass the args directly into the Fragment.
I found a solution to my question, which i am sure not the correct way to do that...
So in RegisterActivity.java i add + modified the following lines (thx to #sturrockad):
public Registration getRegistrationData(){
return this.regData;
}
public void onEmailRegistered(int position, Registration regData){
this.regData = regData;
viewPager.setCurrentItem(position, true);
}
Then in RegisterTwoFragments.java (or in the Fragment to which i want to receive the Object):
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_register_two, container, false);
regData = ((RegisterActivity) getActivity()).getRegistrationData();
...
I used to set object with Pacelable or Serializable to transfer, but whenever I add other variables to object(model), I have to register it all. It's so inconvenient.
It's super easy to transfer object between activities or fragments.
Android DataCache
put your data object to KimchiDataCache instance in your activity or fragment.
User userItem = new User(1, "KimKevin"); // Sample Model
KimchiDataCache.getInstance().put(userItem);
// add your activity or fragment
Get your data object in your activity of fragment that you added.
public class MainFragment extends Fragment{
private User userItem;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
userItem = KimchiDataCache.getInstance().get(User.class);
}

Updating Fragments - FrameStatePagerAdapter and HTTP Calls

I have been searching for an answer to my problem, but I seem to get none, despite of how many tutorials I followed, how many questions I've gone through and how many things I've tried to do what I want. Basically, I stumbled upon some good tips, and still couldn't manage to do what wanted.
THE PROBLEM
I am creating an Android Application that will use Fragments (alongside with tabs). In these fragments, I have crucial information relating the application, such as text boxes, and buttons. However, I want to do something really simple, which is updating one of my fragments as I come back to it (imagine I swipe back to a fragment, and I update it with the relevant information). Where is the information stored? On a node.js server, to which I call every time I want information. So for that, I created the following structure.
THE STRUCTURE
First of all, I started off creating my Activity.
public class CentralActivity extends FragmentActivity {
CentralPagerAdapter mCentralActivity;
ViewPager mViewPager;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_central);
tabHandler();
}
public void tabHandler() {
mCentralActivity = new CentralPagerAdapter(getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.CentralPager);
mViewPager.setAdapter(mCentralActivity);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
getActionBar().setSelectedNavigationItem(position);
}
});
//Action Bar Stuff
}
}
With this said, I need my CentralPagerAdapter, which I created as follows.
public class CentralPagerAdapter extends FragmentStatePagerAdapter {
private int nSwipes = 3;
public CentralPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new CentralFragment();
Bundle args = new Bundle();
args.putInt(CentralFragment.ARG_OBJECT, i + 1);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return nSwipes;
}
}
And now, my fragment, which is only a class that contains all of my views, and options and so on.
public class CentralFragment extends Fragment {
public static final String ARG_OBJECT = "object";
private View rootView;
private RESTFunction currentFunction;
//Has the info I want
private ArrayList<Integer> tickets = new ArrayList<Integer>();
#SuppressLint("HandlerLeak")
private Handler threadConnectionHandler = new Handler() {
#Override
public void handleMessage(Message msg) {
switch (currentFunction) {
case GET_CLIENT_TICKETS:
handleGetTickets(msg);
break;
case BUY_CLIENT_TICKETS:
break;
default:
break;
}
}
};
#Override
public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
Bundle savedInstanceState) {
final Bundle args = getArguments();
handleFragments(inflater, container);
getTicketInfo(null);
return rootView;
}
private void handleFragments(LayoutInflater inflater, ViewGroup container) {
Bundle args = getArguments();
if (args.getInt(ARG_OBJECT) == 1) {
rootView = inflater.inflate(R.layout.fragment_show_tickets,
container, false);
showTicketsHandler();
} else if (args.getInt(ARG_OBJECT) == 2) {
rootView = inflater.inflate(R.layout.fragment_buy_tickets,
container, false);
buyTicketsHandler();
} else {
rootView = inflater.inflate(R.layout.fragment_history_tickets,
container, false);
}
}
public void showTicketsHandler() {
//Get stuff from the tickets array that the REST call will handle
//And set them to boxes or radio buttons
}
public void buyTicketsHandler() {
//Get stuff from the tickets array that the REST call will handle
//And set them to boxes or radio buttons
//As well as button click listeners
}
public void getTicketInfo(ProgressDialog progDialog) {
//Connect to the thread to get the information
//In this case, I have no parameters
ConnectionThread dataThread = new ConnectionThread("myLink", Method.GET, null, threadConnectionHandler, progDialog);
dataThread.start();
}
//Get stuff from the resulting JSON and store it in the tickets ArrayList
private void handleGetTickets(Message msg) {
JSONObject ticketListing = (JSONObject) msg.obj;
try {
tickets.add(ticketListing.getInt("t1"));
tickets.add(ticketListing.getInt("t2"));
tickets.add(ticketListing.getInt("t3"));
} catch (JSONException e) {
e.printStackTrace();
}
}
}
And then, I have my thread..
public class ConnectionThread extends Thread {
private ConnectionRunnable runConnection;
private Handler mHandler;
private ProgressDialog progDialog;
public ConnectionThread(String link, Method method, ArrayList<NameValuePair> payload, Handler handler, ProgressDialog progDialog) {
runConnection = new ConnectionRunnable(link, method.toString(), payload);
mHandler = handler;
this.progDialog = progDialog;
}
#Override
public void run() {
runConnection.run();
threadMsg();
if(progDialog != null)
progDialog.dismiss();
}
public JSONObject getJSON() {
return runConnection.getResultObject();
}
private void threadMsg() {
Message msgObj = mHandler.obtainMessage();
msgObj.obj = getJSON();
mHandler.sendMessage(msgObj);
}
}
And ConnectionRunnable is where I run my HttpURLConnection.
SO WHAT DO I NEED?
Basically, what I'm trying to do, is to get the ticket information from the ConnectionThread BEFORE I load all my view and update them. Plus, I want to be able to swipe back and forth, and update my information on the array as I swipe through the screens (if I go to the second screen, the tickets will update, and if I come back to the first, they will re-update). So basically, call the ConnectionThread everytime I swipe around. If that is possible that, is.
WHAT HAVE I TRIED?
I've tried several things already, and all of them didn't actually help..
The usage of ProgressDialogs to stop the UI Thread on the onCreateView method of the fragment (no use, because it returns the rootView before it handles everything);
Making the UI Thread sleep for 1 second (I don't know why, it blocks all of them);
Overriding the instantiateMethod() of the Adapter, although I think I didn't do it correctly;
Overriding the saveState() of the Adapter, in order to prevent its saved states, and to then get new ticket information;
Giving the fragments tags to update their rootViews on the Adapter, but to no avail;
Getting the information in the activity, and everytime I make a purchase (second fragment), restart the whole activity to get the tickets, which I believe is a really, really bad solution.
I've read several articles, and I still couldn't find my answers.. It's really frustrating. Because it's something so simple, however, the fact that I have to run the HTTP calls on a different thread delays the whole UI updating process.
I've also read the AsyncTask's method. However, I feel like both Threads and AsyncTasks end up in the same.
WHAT TO DO NOW?
Well, that's what I was hoping to find. Because it ends up being annoying as it is.
POSSIBLE REASONS
Is it because I'm separating all classes into spread files, therefore making my work difficult?
Thank you for your time, guys, hope we can find a solution or something.
THE EDIT
So basically, after 4 hours of reading documents and tutorials, I figured that what I needed was setOffscreenPageLimit(int). However, it can't be set to 0, so I will have to do with a setOnPageChangeListener. Now, to figure how to refresh the fragment, and I'll be as good as new.
Alright, it works perfectly! Basically, I did this:
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
((CentralFragment)((CentralPagerAdapter) mViewPager.getAdapter()).instantiateItem(mViewPager, position)).refresh();
getActionBar().setSelectedNavigationItem(position);
}
});
Where my .refresh is:
public void refresh() {
Bundle args = getArguments();
if (args.getInt(ARG_OBJECT) == 0) {
getTicketInfo(0);
} else if (args.getInt(ARG_OBJECT) == 1) {
getTicketInfo(1);
buyTicketsHandler();
} else {
//To Handle Later
}
}
It's as simple as refreshing the page before you go to it. Why didn't I remember this before..? So, here's the reference for those who ever need this!

Categories