I'm following steps on
http://developer.android.com/training/basics/fragments/creating.html#AddInLayout
and I can't seem to figure out why they stated, "One difference when creating a Fragment is that you must use the onCreateView() callback to define the layout." but didn't use onCreateView for the HeadlinesFragment, only the ArticleFragment. They both seem to set a layout so I don't understand why onCreateView is not used. Also, is the fragment_container only the xml file? I'm guessing it's only used in the MainActivity. Code is posted on
Android Fragment Basics Tutorial
except code for articleFragment which I posted below. So far, I think I understand that you have a class that extends fragmentactivity that holds fragment container and methods to switch out other fragments. Then you have the fragments that are classes that extends fragment or listFragment? However, the site,
http://developer.samsung.com/android/technical-docs/Using-Fragments-to-Build-User-Interfaces-in-Android
shows examples of activities that DONT extend fragment activity but rather just activity and it's supposed to hold the other fragment activities.
This site:
http://developer.android.com/reference/android/app/Fragment.html
showed more information on fragments but the lifecycle doesn't clarify when to use OnCreate vs onCreateView, I'm thinking it's something in regards to the layouts but it states that onCreate starts fragment too so I'm not positive on which to use.
THANKS!!!!!
package com.example.android.fragments;
import android.support.v4.app.Fragment;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
public class ArticleFragment extends Fragment {
final static String ARG_POSITION = "position";
int mCurrentPosition = -1;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// If activity recreated (such as from screen rotate), restore
// the previous article selection set by onSaveInstanceState().
// This is primarily necessary when in the two-pane layout.
if (savedInstanceState != null) {
mCurrentPosition = savedInstanceState.getInt(ARG_POSITION);
}
// Inflate the layout for this fragment
return inflater.inflate(R.layout.article_view, container, false);
}
#Override
public void onStart() {
super.onStart();
// During startup, check if there are arguments passed to the fragment.
// onStart is a good place to do this because the layout has already been
// applied to the fragment at this point so we can safely call the method
// below that sets the article text.
Bundle args = getArguments();
if (args != null) {
// Set article based on argument passed in
updateArticleView(args.getInt(ARG_POSITION));
} else if (mCurrentPosition != -1) {
// Set article based on saved instance state defined during onCreateView
updateArticleView(mCurrentPosition);
}
}
public void updateArticleView(int position) {
TextView article = (TextView) getActivity().findViewById(R.id.article_fragment);
article.setText(Ipsum.Articles[position]);
mCurrentPosition = position;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the current article selection in case we need to recreate the fragment
outState.putInt(ARG_POSITION, mCurrentPosition);
}
}
If you look closely at the code you will see a difference between HeadlinesFragment and ArticleFragment:
public class HeadlinesFragment extends ListFragment
public class ArticleFragment extends Fragment
The difference of course is that HeadlinesFragment is a ListFragment rather than just a plain ol' Fragment. ListFragment already implements a default onCreateView() that loads a layout with a ListView. If you want to override the default behavior, you can write your own onCreateView() method to make a more sophisticated layout.
I learned that since the fragment lifecycle is tied to the activity onCreate creates both the activity and the fragment. onCreate is used to set activity layout where onCreateView sets the fragment layout.
the reason headlinesFragment didn't have onCreateView is because since it's a list it has a predefined layout and thus doesn't need to be overridden.
Related
I'm trying to follow this tutorial
I have a project that uses the Sidebar Navigation, so I have one MainActivity and multiple Fragments. At ~6:20 into the video, you can see the following code:
PersonListAdapter adapter = new PersonListAdapter(
this,
R.layout.adapter_view_layout,
peopleList);
The constructor for the PersonListAdapter Class is:
public PersonListAdapter(Context context, int resource, ArrayList<Attacks> objects) {
super(context, resource, objects);
this.mContext = mContext;
mResource = resource;
}
The problem lies with Context.
If I use the word "this", there is a red line.
If I replace
"this" with "getActivity()", there is no red line, but the app
crashes when I run it.
I've also tried "this.getContext()" and "this.getActivity()"
I have also tried replacing "this" with "getActivity().getApplicationContext()", and the app crashes.
The tutorial uses MainActivity.java, but my code is in FragmentCharacters.java. I don't know what I'm supposed to write in place of "this", or if I need to change something in the PersonListAdapter class for Context.
You cannot use a Fragment as a Context, because Fragment doesn't inherit from Context.
However, if you consult the Fragment lifecycle, you can see that the Fragment has access to its host Activity at any time between the lifecycle callbacks OnActivityCreated() and onDestroyView(). If you try to access the Activity before OnActivityCreated(), for example, it will probably return null.
So make sure you are calling getActivity() from within onActivityCreated() or later, which will make sure your Activity is available.
UPDATE, I Provided a Case Example inside the Code Snippets as well, and I chose "FragmentName" as Fragment name for example.
First Look at This Fragment Structure.
I Added [mAdapter] in Both onCreate and onCreateView
And I Added FragmentName.this for the Forth argument
The Reason is, You can send data from The Adapter to Other Activities with it, for Example FragmentName.mAdapter.getLayoutPosition()
But, Let's assume We have an ImageView which is In MainActivity and we want to use it in our Adapter, So let's Establish an ImageView In our Fragment as well, Notice I Declared the ImageView Inside onCreate
And, For Another Example, Let's Assume we Have a Public Void at the End of our Fragment as Well, It can be Literally Anything. I Named it ExampleClass
/////////FIRST TAKE A LOOK AT FRAGMENT//////////
public class FragmentName extends Fragment {
PersonListAdapter adapter;
ImageView imageView; // For Example thi ImageView is from MainActivity
public FragmentName() {
...
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
adapter = new PersonListAdapter(getContext(), R.layout.adapter_view_layout, peopleList, FragmentName.this);
imageView = (ImageView) getActivity().findViewById(R.id.ImageView);
// This Imageview is in Another Activity, Like MainActivity
// So we Need to Find it Using 'getActivity()'
...
}
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, #Nullable ViewGroup container, #Nullable Bundle savedInstanceState) {
adapter = new PersonListAdapter(getContext(), R.layout.adapter_view_layout, peopleList, FragmentName.this);
}
}
public void ExampleClass(int color, ...) {
...
}
////////////////////////////////////////////////////////////////
Now, Let's take this Example into our Adapter as well, to Show how it can be Used.
But, In the Adapter Use [FragmentName], Instead of [Fragment] like Below:
///////////NOW INSIDE YOUR ADAPTER/////////////
public class PersonListAdapter extends RecyclerView.Adapter < PersonListAdapter.myViewHolder > {
FragmentName myFragment; // SEE WHAT HAPPENDED HERE?
...
public PersonListAdapter(Context context, int resource, ArrayList < Attacks > objects, FragmentName fragment) {
super(context, resource, objects);
this.mContext = mContext;
mResource = resource;
this.myFragment = fragment
}
#Override
public PersonListAdapter.myViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Example use of myFragment
// Lets Execute ExmpleClass inside the Fragment
myFragment.ExampleClass(int color, ...);
// Let's Use the ImageView from MainActivity Here
myFragment.imageView.setImageRresource(...);
...
}
...
// YOU CAN NOW USE "myFragment" As a Context In your Adapter
The Good Part about this is That You can Use Fragment As CONTEXT in Your PersonListAdapter
Update: The second Code, onCreateViewHolder is wrong, it has to be inside a ClickListener in ViewHolder or onBindViewHolder
I'm having trouble with restoring state of a View inside a ViewPager. The content of the ViewPager is a view extending FrameLayout.
The problem is the FrameLayout.onRestoreInstanceState() is not being called if added programmatically into the ViewPager
Here's the code of my Activity.java
private ViewPager vPager;
private MainPagerAdapter mAdapter;
#Override
protected void onCreate(#Nullable Bundle savedInstanceState) {
setContentView(R.layout.activity_layout);
// all the findViewById stuff
CustomView cv1 = new CustomView(this);
CustomView cv2 = new CustomView(this);
cv1.setId(R.id.custom_view_id_1);
cv2.setId(R.id.custom_view_id_2);
mAdapter = MainPagerAdapter();
mAdapter.addView(cv1);
mAdapter.addView(cv2);
vPager.setAdapter(mAdapter);
}
MainPagerAdapter is a class from the accepted answer of this question
Source code for CustomView.java
#Override
protected Parcelable onSaveInstanceState() {
Log.d(TAG, "onSaveInstanceState() called");
}
#Override
protected void onRestoreInstanceState(Parcelable state) {
Log.d(TAG, "onRestoreInstanceState() called");
}
Here's my findings so far:
onSaveInstanceState() will be called but onRestoreInstanceState() is not
When I tried to add the View directly to the root of the Activity, it calls both functions.
I found out that Activity's onRestoreState function will be called before the ViewPager calls the adapter's instantiateItem() function. So when the activity restore its state, the ViewPager doesn't have any children yet, thus the savedState doesn't belong to anyone
So I figure out that I need to make one of two things to work:
Make sure the ViewPager instantiate the item before trying to restore the state, or
Calls the CustomView's onRestoreInstanceState() manually.
I somehow managed to make option number 2, but is there any way to do option number 1?
If I understood your question, you can save your ViewPager items state using mPage.setOffscreenPageLimit(4); 4 is the number of my Fragments inside ViewPager.
I want to keep my application thin.
Problem: I would like to reuse my Fragment class code to create 3 different instances in the ViewPager which will have 3 pages. Each Fragment will have a different ImageView or background Drawable. What are best practices regarding this? I noticed that using factory methods like here seem to be good, any other alternatives?
I have one Fragment which has the following methods:
Fragment.java
public static Fragment newInstance(Context context) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment, null);
return root;
}
I have a ViewPagerAdapter class which has the following methods:
ViewPagerAdapter.java
public ViewPagerAdapter(Context context, FragmentManager fm) {
super(fm);
mContext = context;
}
#Override
public Fragment getItem(int position) {
return new FragmentTutorial().newInstance(mContext);
}
#Override
public int getCount() {
return totalPage;
}
What I've found is the "best" way to do it (in my opinion, of course) is to do the following:
Have the fragment contain methods to set the customizable data (background, text, etc)
Note: Be careful of trying to load the data in when first creating the fragment. You may be able to set the data before onCreateView() even runs, or at other times it may run after onCreateView(). I personally use a boolean to check if the data has been set. Inside onCreateView() [or onActivityCreated()], I check if the data has been set already. If it has, load in the data. Alternatively, while setting the data, I check if the views have been created/cached already. This is done by simply having variables to cache the data, say private ImageView mBackgroundView. If the view is not null, then I safely set the data on the views.
The above is also an alternative to using newInstance, although both methods work pretty well. However, for more flexibility, I only use newInstance if a) the data is already known before the fragment has to be inserted and b) the data doesn't need to change according to input from elsewhere much.
Let the ViewPager handle all the data
Pass in all the data - a list of ImageViews, a array of Strings, define where all the data is in Resources, etc - at the very beginning [say, in the constructor]
Have the ViewPager create an ArrayList of the fragments- set up each fragment as early as possible (say when first getting all the data) and add it to the list
Let getCount() just use the size of the list
Let getItem() just get the item in the list at the position
Note: If you have any dynamic data, set it up in the getItem() method. Furthermore, you can always add more data+fragments during runtime as well [just notify the adapter that the dataset has been changed]
Essentially, the fragment is like a simple servant- it does simply the least work necessary. If it doesn't have to handle choosing the data, all the better. It'll thus be far more flexible. Just give methods to set the data/views appropriately on the fragment. Now, the ArrayAdapter can do all the grimy hard work with managing the data and giving it to the appropriate fragment. Take advantage of that.
Now, note that this is assuming you want to use a single layout but want to change different aspects of that layout (texts, background, etc). If you want to make a master fragment class that can use any sort of defined layout, you can but note that it decreases the runtime flexibility (how can you change the text or background to something you get from the internet? You simply can't if you only can define and choose from pre-set layouts).
Either way, the ArrayAdapter should take care of all the different data while the fragment simply does as it's designed to do, in a more flexible manner preferably.
Edit:
Here is the project where I most recently implemented this sort of pattern. Note that it has far more to it, so I'll replace it with some not-so-pseudo pseudo-code in the morning/afternoon.
ViewPager [a bit sloppy with all the different things I was trying to do, including extending from a FragmentStatePagerAdapter without actually using any of the specific features of a StatePagerAdapter. In other words, I still need to work on the lifecycle implementations everywhere]
Fragment [Also may be a bit sloppy but shows the pattern still]
The object (actually another fragment) that uses the ViewPager [it's actually a "VerticalViewpager" from a library, but other than the animations and direction to change the current fragment, it's exactly the same- particularly code-wise]
Edit2:
Here is a more (if overly) simplified example of the pattern described above.
Disclaimer: The following code has absolutely no lifecycle management implementations and is older code that has been untouched since around August '14
Fragment simply allows the user of the fragment to set the background color and the text of the single TextView
Link to BaseFragment
Link to layout file
The adapter creates three instances of the fragment and sets the background color and text of each. Each fragment's text, color, and total fragments is hard coded.
Link to Activity+adapter
Link to layout file
Now, here are the exact relevant portions of the code:
BaseFragment
// Note: Found out later can extend normal Fragments but must use v13 adapter
public class BaseFragment extends android.support.v4.app.Fragment {
FrameLayout mMainLayout; // The parent layout
int mNewColor = 0; // The new bg color, set from activity
String mNewText = ""; // The new text, set from activity
TextView mMainText; // The only textview in this fragment
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
// Inflate the fragment's layout
View view = inflater.inflate(R.layout.fragment_base,container,false);
// Save the textview for further editing
mMainText = (TextView) view.findViewById(R.id.textView);
// Save the framelayout to change background color later
mMainLayout = (FrameLayout) view.findViewById(R.id.mainLayout);
return view;
}
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// If there is new text or color assigned, set em
if(mNewText != ""){
mMainText.setText(mNewText);
}
if(mNewColor != 0){
mMainLayout.setBackgroundColor(mNewColor);
}
}
#Override
public void onStart(){
super.onStart();
}
// Simply indicate to change the text of the fragment
public void changeText(String newText){
mNewText=newText;
}
// Simply indicate to change the background color of the fragment
public void changeBG(int color) {
// If no color was passed, then set background to white
if(color == 0)
{
mNewColor=getResources().getColor(R.color.white);
}
// else set the color to what was passed in
else{
mNewColor=color;
}
}
}
MyAdapter
class MyAdapter extends FragmentPagerAdapter{
// Three simple fragments
BaseFragment fragA;
BaseFragment fragB;
BaseFragment fragC;
public MyAdapter(FragmentManager fm) {
super(fm);
}
public void setFragments(Context c){
// Set up the simple base fragments
fragA = new BaseFragment();
fragB = new BaseFragment();
fragC = new BaseFragment();
Resources res = c.getResources();
fragA.changeText("This is Fragment A!");
fragB.changeText("This is Fragment B!");
fragC.changeText("This is Fragment C!");
fragA.changeBG(res.getColor(R.color.dev_blue));
fragB.changeBG(res.getColor(R.color.dev_green));
fragC.changeBG(res.getColor(R.color.dev_orange));
}
#Override
public Fragment getItem(int position) {
// TODO: Make this more efficient, use a list or such, also comment more
Fragment frag = null;
if(position == 0){
frag = fragA;
}
else if(position == 1){
frag = fragB;
}
else if(position == 2){
frag = fragC;
}
return frag;
}
#Override
public int getCount() {
return 3;
}
}
You need to pass some sort of id along with newInstance() while creating instance. And according to that id you can use if..else to choose layout file.
See my reference code below:
int id;
public static Fragment newInstance(Context context, int id) {
FragmentTutorial f = new FragmentTutorial();
Bundle args = new Bundle();
this.id = id;
return f;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
if(id == 1)
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment1, null);
else
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.fragment2, null);
return root;
}
Can't you just introduce fields to the Fragment class to account for the variances in background, etc. and add them to its constructor? Then in getItem instantiate the Fragment class with different values depending on the value of position.
I'm trying to create an activity that adds a dynamic fragment at runtime. From that fragment I want to be able to open six other fragments on button click. [Going to use a case to implement this most likely]
Think of it as a windows 8 UI; with 6 buttons, each one opens a new fragment.
Unfortunately I have no idea how to go about this. I can't seem to get the button to pass data back to the main activity. I've also lost quite a bit of my code due to a git mishap. Here's what I recreated.
If you have any tips on coding style, syntax, java, OO- those are all welcome too. I'm coming from a C background. My end goal would be to create a replaceFragment(Frag) method for some easy syntactic sugar later on. Though I couldn't implement that with any success so far.
Another small question with fragments - I'm trying to add them dynamically at run-time - do I need to create all of them at run time? So each one needs a .add [Drink fragment, Menu fragment] or do I just need to do the .replace
SingleFragmentActivity.java
public abstract class SingleFragmentActivity extends FragmentActivity{
protected abstract Fragment createFragment();
FragmentManager fm = getSupportFragmentManager();
#Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); //Lock screen orientation for app
Fragment frag = fm.findFragmentById(R.id.fragment_container);
fm.beginTransaction()
.add(R.id.fragment_container,frag)
.commit();
}
}
Customer_Activity.java
public class Customer_Activity extends SingleFragmentActivity {
public static Context appContext;
#Override
protected Fragment createFragment() {
return new CustomerSelectionFragment();
}
}
CustomerSelectionFragment
public class CustomerSelectionFragment extends Fragment implements OnClickListener{
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.customer_selection_fragment, container, false);
//Buttons should be placed here?
Button btnDrink = (Button) v.findViewById(R.id.Drink);
btnDrink.setOnClickListener(this);
Button btnMenu = (Button) v.findViewById(R.id.Menu);
btnDrink.setOnClickListener(this);
return v;
}
//implement the onClick method here
public void onClick(View v) {
// Perform action on click
switch(v.getId()) {
case R.id.Drink:
//Not sure how to pass "Create Drink Fragment to activity?
break;
case R.id.Menu:
//Pass Create Menu fragment to activity?
break;
}
}
}
Totally ok with people editing my post for good-faith reasons [clarity, etc].
Any communication between fragments should be done via activity . Here is the link to developers site http://developer.android.com/training/basics/fragments/communicating.html , the tutorial is about communicating between fragments and pretty much explains everything.
I have a fragment that is a "timer" that I can add anywhere. In the fragment I change a textView programatically, and it runs beautifully. My problem is when it comes to using a view from the layout inflated by the constructor(? Not sure if that's the right terminology) in another method below it.
public class Timer_fragment extends android.support.v4.app.Fragment {
int testpins;
String testedpin;
TextView text;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.timer_frag, container, false);
TextView text = (TextView) v.findViewById(R.id.pwd_status);
text.setText("Setting text in fragment not main");
/* set the TextView's text, click listeners, etc. */
updateStatus();
return v;
}
All of that code works with no errors but when I try to add this method:
private void updateStatus() {
TextView text = (TextView) findViewById(R.id.pwd_status);
testPin();
text.setText(testedpin);
}
I get a red line under findViewById saying The method findViewById(int) is undefined for the type Timer_fragment.
I thought about inflating the view in all of my methods and not returning them, but surely that would affect performance somehow right?
Just tried inflating the layout before using the view but I get an error on the word inflater and container saying that they can't be resolved.
Am I going about this correctly?
You already have a member variable in the scope of your Fragment called text. Don't re-declare it in your methods, just assign it.
text = (TextView) v.findViewById(R.id.pwd_status);
and
private void upateStatus() {
testPin();
text.setText(testedpin);
}
The method 'findViewById' is provided by the activity. While this class extends Fragment, you will not have access to activity related method calls unless you provide the activity to the fragment. Check out: http://developer.android.com/reference/android/app/Activity.html#findViewById(int)
Basically, either pass in the instance of the activity to the Timer_fragment:
private final Activity _activity;
Timer_fragment(Activity activity)
{
_activity = activity;
}
...
private void updateStatus()
{
TextView text = (TextView) _activity.findViewById(R.id.pwd_status);
testPin();
text.setText(testedpin);
}
Or set the text of the view from within whichever activity is being used, and not from within the timer class.
Just replace findViewById with getActivity().findViewById.
findViewById method is defined inside the Activity class. Fragments aren’t activites. But the fragment can get a reference to the Activity that added it to a screen using the method getActivity.