I have a fragment ArticleFragment which is passed an Article object.
The ArticleFragment is usually displayed in an Activity but I would like to reuse the fragment inside a ListView. Each row of the ListView should hold an instance of ArticleFragment.
I know that I can customize a row with the getView() method of the adapter but the UI displayed by the ArticleFragment is very much alike as that of a row so it would be annoying to have to update it at two positions if the UI changes.
ArticleFragment.java
public class ArticleFragment extends SherlockFragment {
static final String ARTICLE_PARCEL_KEY = "parcelable_article";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
protected static ArticleFragment newInstance(Article article) {
ArticleFragment f = new ArticleFragment();
Bundle args = new Bundle(Article.class.getClassLoader());
args.putParcelable(ARTICLE_PARCEL_KEY, article);
f.setArguments(args);
return f;
}
Article getArticle() {
return getArguments().getParcelable(ARTICLE_PARCEL_KEY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View layout = (View) inflater.inflate(R.layout.article, container, false);
ArticleImagesFragment imagesFragment = ArticleImagesFragment.newInstance(getArticle().getImages());
FragmentTransaction ft = getChildFragmentManager().beginTransaction();
ft.add(R.id.images_fragment_container, imagesFragment);
if (getActivity() instanceof ArticleActivity) {
DisqusCommentsFragment disqusFragment = DisqusCommentsFragment.newInstance(getArticle().getCommentsUrl());
ft.add(R.id.comments_fragment_container, disqusFragment);
}
ft.commit();
return layout;
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
Article article = getArticle();
TextView kickerView = (TextView) view.findViewById(R.id.kicker);
kickerView.setText(article.getKicker().toUpperCase());
TextView titleView = (TextView) view.findViewById(R.id.title);
titleView.setText(article.getTitle());
String commentsText = "Jetzt kommentieren!";
if (article.getNumComments() == 1) {
commentsText = article.getNumComments().toString() + " Kommentar";
} else if (article.getNumComments() > 1) {
commentsText = article.getNumComments().toString() + " Kommentare";
}
TextView numCommentsView = (TextView) view.findViewById(R.id.num_comments);
numCommentsView.setText(commentsText);
TextView pubDateView = (TextView) view.findViewById(R.id.pub_date);
String dateText = new SimpleDateFormat("dd. MMMM, hh:mm").format(article.getPubDate());
pubDateView.setText(dateText);
TextView authorNameView = (TextView) view.findViewById(R.id.author_name);
authorNameView.setText(article.getAuthorName());
TextView articleTextView = (TextView) view.findViewById(R.id.text);
articleTextView.setText(Html.fromHtml(article.getHtmlContent()));
}
}
I noticed that it's possible to pass a custom row layout to the ArrayAdapter but that would again duplicate the logic for displaying the UI of an Article. Instead I would prefer to pass a fragment to the constructor of the adapter and use it to display the row.
Is it possible to achieve this with an ArrayAdapter?
If not what would be the right path to take to achieve the reuse of the ArticleFragment in a ListView?
Thanks for your help!
Related
UPDATE: This is my current code, however it breaks after I re-click the same button....
Cause: java.lang.IllegalStateException: Fragment already active
I have a main_activity.xml split into Frame layouts.
The upper frame layout contains an edit text and 3 buttons (small, medium, large).
The lower frame layout contains a text view to populate the text of the edit text in either small, medium or large font.
I put the text into a bundle and am attempting to reopen that bundle in the corresponding fragment once the button is clicked.... however, the bundle does not contain any text information from the text according to my debugger.
Here is my code with the medium, large buttons removed
Any help, please?
MainActivity:
public class MainActivity extends AppCompatActivity {
TextView textView;
Button bSmall;
Small fSmall = new Small();
Bundle bundle = new Bundle();
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if(findViewById(R.id.fragment)!=null){
if(savedInstanceState!=null){
return;
}
}
final EditText editText = (EditText) findViewById(R.id.type_here);
final Button bSmall = (Button)findViewById(R.id.small);
Button bMedium = (Button)findViewById(R.id.medium);
bSmall.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message_small = editText.getText().toString();
small = message_small;
bundle.putString("message_small", message_small);
fSmall.setArguments(bundle);
FragmentManager fm =getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment, fSmall).commit();
}
});
bMedium.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
String message_medium = editText.getText().toString();
bundle.putString("message_medium", message_medium);
fMedium.setArguments(bundle);
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment, fMedium).commit();
}
});
}
public String getMyData(){
return small;
}
}
Small:
public class Small extends Fragment {
EditText editText;
View myView;
public Small() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
myView = inflater.inflate(R.layout.fragment_small, container, false);
Bundle bundle = new Bundle();
bundle = getArguments();
MainActivity activity = (MainActivity)getActivity();
//String dataFromMainActivity = activity.getMyData();
String myString = bundle.getString("message_small");
TextView set = myView.findViewById(R.id.small_text);
set.setText(myString);
// Inflate the layout for this fragment
return myView;
}
}
You forgot to add the bundle to the fragment.
You need to do something like this:
Small fSmall = new Small();
...
bundle.putString("message_small", message_small);
fSmall.setArguments(bundle);
FragmentManager fm =getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment, fSmall);
I think you forgot to add your bundle to your Fragment before calling the fragment manager.
You should try something like this:
Small fSmall = new Small();
Bundle bundle = new Bundle();
bundle.putString("message_small", message_small); //parameters are (key, value).
fSmall.setArguments(bundle);
getSupportFragmentManager().beginTransaction().replace(R.id.fragment, fSmall).commit();
In your second fragment, you should check if myString is not null or empty.
String myString = getArguments().getString("message_small");
if (myString == null) {
Log.e("TAG", "Error: null argument");
}
EDIT I see another problem here. You are accessing varaibles that have not been instatiated. You should inflate your layout before calling findViewById() or it will return a NullPointerException.
Update your Small class like this :
public class Small extends Fragment {
private EditText editText;
View myView;
public Small() {
// Required empty public constructor
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the layout for this fragment
myView = inflater.inflate(R.layout.fragment_small, container, false);
String myString = getArguments().getString("message_small");
// Here, myView is != null
TextView editText = myView.findViewById(R.id.small_text);
// Here, editText is != null
editText.setText(myString);
return myView;
}
}
Best
I want to change the text of my TextView element, which is defined in the corresponding QuoteFragment.class file and the related layout.xml file. You can see the code of the method in my main activity:
private void forwardToQuoteFragment(Quote quote){
quoteFragment = QuoteFragment.newInstance(quote);
View view = quoteFragment.getView();
TextView tv = (TextView) view.findViewById(R.id.quoteFragmentHeader);
tv.setText("Quote (" + quote.getId() + "): " + quote.getQuote());
android.support.v4.app.FragmentManager fm = getSupportFragmentManager();
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.container, quoteFragment);
ft.commit();
}
My debugger tells me, that the view variable is null, therefore I got an NPE. It will make no difference, if I created the view-property in my QuoteFragment.class, which you can see below:
public class QuoteFragment extends android.support.v4.app.Fragment {
public static final String QUOTE_FRAGMENT_TAG = "QuoteFragment";
public static final String QUOTE_ID = "quoteId";
private View view;
private long quoteId;
public QuoteFragment(){
// required
}
// factory to set arguments
public static QuoteFragment newInstance(Quote quote) {
QuoteFragment fragment = new QuoteFragment();
Bundle args = new Bundle();
args.putLong(QUOTE_ID, quote.getId());
fragment.setArguments(args);
return fragment;
}
#Override
public void onAttach(Context context){
super.onAttach(context);
Log.i(QUOTE_FRAGMENT_TAG, "onAttach()");
}
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
Log.i(QUOTE_FRAGMENT_TAG, "onCreate()");
setHasOptionsMenu(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
view = inflater.inflate(R.layout.fragment_quote, container, false);
return view;
}
public void setQuoteId(long id){
this.quoteId = id;
}
public long getQuoteId() {
return quoteId;
}
public View getView(){
return this.view;
}
}
What is the best way to solve this issue? What did I overlooked?
You can't get the View, because the View was not drawn to the screen yet:
onCreateView() inside your Fragment has not been called, yet, but you are already trying to access the TextView which only will be created when onCreateView() is called.
You need to set the text inside the OnCreateView() method like so
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState){
View view = inflater.inflate(R.layout.fragment_quote, container, false);
TextView tv = (TextView) view.findViewById(R.id.quoteFragmentHeader);
tv.setText("Quote (" + quote.getId() + "): " + quote.getQuote());
return view;
}
I'm more googling to find how can i pass simple view as an object to fragment, but i can't.
for example in MainActivity i have simple view as :
TextView text = (TextView) findviewById(R.id.tv_text);
now i want to pass that to fragment. this below code is my attach Fragment on MainActivity
MainActivity :
public void attachFragment() {
fts = getActivity().getFragmentManager().beginTransaction();
mFragment = new FragmentMarketDetail();
fts.replace(R.id.cardsLine, mFragment, "FragmentMarketDetail");
fts.commit();
}
and this is my Fragment:
public class FragmentMarketDetail extends Fragment implements ObservableScrollViewCallbacks {
public static final String SCROLLVIEW_STATE = "scrollviewState";
private ObservableScrollView scrollViewTest;
private Context context;
private int scrollY;
public static FragmentMarketDetail newInstance() {
FragmentMarketDetail fragmentFirst = new FragmentMarketDetail();
return fragmentFirst;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_online_categories, container, false);
scrollViewTest = (ObservableScrollView) view.findViewById(R.id.scrollViewTest);
scrollViewTest.setScrollViewCallbacks(this);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
context = getActivity().getBaseContext();
}
}
It wouldn't be good practise to pass a view that way. If you want to access the view in your activity from within your fragment class, use getActivity() to access the activity to which your fragment is attached, and from there you find your TextView.
TextView text = (TextView) getActivity().findViewById(R.id.tv_text);
How about adding a set function in your custom fragment
e.g.
public void setTextView(TextView tv){
this.tv = tv
}
and then calling it after
mFragment = new FragmentMarketDetail();
mFragment.setTextView(textView)
Find fragment by tag and invoke a function on it:
mFragment = (FragmentMarketDetail ) getActivity().getFragmentManager().findFragmentByTag(FragmentMarketDetail .class.getSimpleName());
mFragment.passTextView(textView);
Of course fragment must be added to backstack.
This question already has answers here:
How to pass values between Fragments
(18 answers)
Closed 6 years ago.
I have Two Dynamic Fragments associated with one activity, I am trying to pass one Text from First Fragment to Second Fragment using Bundle, but I am getting Null Pointer Exception. Is it the right way to pass String between two fragments? Below is my code :
First Fragment
public class FirstFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.content_main, container, false);
String text = "GetThisStringInSecondFragment";
TextView txtView = null;
txtView = (TextView) view.findViewById(R.id.firstfragmenttext);
txtView.setText(text);
Bundle bundle = new Bundle();
bundle.putString("HI", text);
return view;
}
}
Second Fragment
public class SecondFragment extends Fragment {
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.content_secondmain,
container, false);
TextView txtView = null;
txtView = (TextView) view.findViewById(R.id.secondfragmenttext);
Bundle bundle = this.getArguments();
String myInt = bundle.getString("HI");
txtView.setText(myInt);
return view;
}
}
Activity
public class MainActivity extends AppCompatActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btnLoad = (Button) findViewById(R.id.btn_load);
View.OnClickListener listener = new View.OnClickListener() {
#Override
public void onClick(View v) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
FirstFragment hello = new FirstFragment();
fragmentTransaction.add(R.id.fragment_container, hello, "HELLO");
fragmentTransaction.commit();
FragmentManager fragmentManager2 = getFragmentManager();
FragmentTransaction fragmentTransaction2 = fragmentManager2.beginTransaction();
SecondFragment hello2 = new SecondFragment();
fragmentTransaction2.add(R.id.fragment_container, hello2, "HELLO");
fragmentTransaction2.commit();
}
};
btnLoad.setOnClickListener(listener);
}
}
You are doing getArguments in second fragment but where are you sending those arguments from first fragment. You can use Interface here and define it in activity. From activity, you can use setArgument() method for second fragment.
check the following link for more detailed information and steps to do that.
https://developer.android.com/training/basics/fragments/communicating.html
Basically I have my fragment
public class FragmentDashboard extends Fragment {
public static FragmentDashboard newInstance() {
FragmentDashboard frag = new FragmentDashboard();
return frag;
}
public void updateData(Object object){
myTextView.setText(object.getField);
//update views with object values
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_dashboard, container, false);
myTextView = (TextView) view.findViewById(R.id.myTextView );
}
}
Then in my activity I need to update data I do:
FragmentDashboard fragmentDashboard = (FragmentDashboard) getSupportFragmentManager().findFragmentById(R.id.layFragment);
fragmentDashboard.updateData(myObject)
This works good if I am making the call on my activity after the Fragment has been displayed (like from an asynctask on completed).
The problem I am running into is having to run the updateData right after I add the fragment on my activity's onCreate().
final FragmentTransaction ft = fragmentManager.beginTransaction();
FragmentDashboard fragmentDashboard = FragmentDashboard.newInstance();
ft.replace(R.id.layFragment, fragmentDashboard);
ft.commit();
fragmentDashboard.updateData(myObject)
Running this I get a NPE because the fragment's onCreateView hasn't been run yet and the myTextView is not initialized
I don't want to add parameters on newInstance as I'd like to avoid making the object as parcelable. Any ideas ?
You can use a local field to contains your data and use it in your onCreateView method :
public class FragmentDashboard extends Fragment {
private Object myData=null;
private TextView myTextView = null;
public static FragmentDashboard newInstance() {
FragmentDashboard frag = new FragmentDashboard();
return frag;
}
public void updateData(Object object){
myData = object;
if(myTextView != null)
myTextView.setText(myData);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_dashboard, container, false);
myTextView = (TextView) view.findViewById(R.id.myTextView );
if(myData != null && myTextView != null)
myTextView.setText(myData);
}
}
It isn't a good practice to set Data like updateData(Object ) . Make your model class parcelable or serializable and pass it in putExtra and get it in onViewCreated.
public static FragmentDashboard newInstance(Object object) {
Bundle args = new Bundle();
args.putParcelable("yourModelClass",object);
FragmentDashboard frag = new FragmentDashboard();
frag.setArguments(args);
return frag;
}
And in onViewCreated
if(getArguments != null)
yourModelClassObject = getArguments().getParcelable("yourModelClass");
if(yourModelClassObject != null)
textView.setText(yourModelClassObject.getField());
I have written code orally . May contain mistakes.