I have a wizard generated app with navigation drawer in android studio 0.8.2
I have created a fragment and added it with newInstance() and I get this error:
com.domain.myapp E/AndroidRuntime﹕ FATAL EXCEPTION: main
java.lang.ClassCastException: com.domain.myapp.MainActivity#422fb8f0 must implement
OnFragmentInteractionListener
I can't find anywhere how to implement this OnFragmentInteractionListener ??
It cannot be found even in android sdk documentation!
MainActivity.java
import android.app.Activity;
import android.app.ActionBar;
import android.app.Fragment;
import android.app.FragmentManager;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.support.v4.widget.DrawerLayout;
public class MainActivity extends Activity
implements NavigationDrawerFragment.NavigationDrawerCallbacks {
/**
* Fragment managing the behaviors, interactions and presentation of the navigation drawer.
*/
private NavigationDrawerFragment mNavigationDrawerFragment;
/**
* Used to store the last screen title. For use in {#link #restoreActionBar()}.
*/
private CharSequence mTitle;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigationDrawerFragment = (NavigationDrawerFragment)
getFragmentManager().findFragmentById(R.id.navigation_drawer);
mTitle = getTitle();
// Set up the drawer.
mNavigationDrawerFragment.setUp(
R.id.navigation_drawer,
(DrawerLayout) findViewById(R.id.drawer_layout));
}
#Override
public void onNavigationDrawerItemSelected(int position) {
// update the main content by replacing fragments
FragmentManager fragmentManager = getFragmentManager();
switch (position) {
case 0: fragmentManager.beginTransaction()
.replace(R.id.container, PlaceholderFragment.newInstance(position + 1))
.commit(); break;
case 1: fragmentManager.beginTransaction()
.replace(R.id.container, AboutFragment.newInstance("test1", "test2"))
.commit(); break; // this crashes the app
case 2: fragmentManager.beginTransaction()
.replace(R.id.container, BrowseQuotesFragment.newInstance("test1", "test2"))
.commit(); break; // this crashes the app
}
}
public void onSectionAttached(int number) {
switch (number) {
case 1:
mTitle = getString(R.string.title_section1);
break;
case 2:
mTitle = getString(R.string.title_section2);
break;
case 3:
mTitle = getString(R.string.title_section3);
break;
}
}
public void restoreActionBar() {
ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
actionBar.setDisplayShowTitleEnabled(true);
actionBar.setTitle(mTitle);
}
#Override
public boolean onCreateOptionsMenu(Menu menu) {
if (!mNavigationDrawerFragment.isDrawerOpen()) {
// Only show items in the action bar relevant to this screen
// if the drawer is not showing. Otherwise, let the drawer
// decide what to show in the action bar.
getMenuInflater().inflate(R.menu.main, menu);
restoreActionBar();
return true;
}
return super.onCreateOptionsMenu(menu);
}
#Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
}
return super.onOptionsItemSelected(item);
}
/**
* A placeholder fragment containing a simple view.
*/
public static class PlaceholderFragment extends Fragment {
/**
* The fragment argument representing the section number for this
* fragment.
*/
private static final String ARG_SECTION_NUMBER = "section_number";
/**
* Returns a new instance of this fragment for the given section
* number.
*/
public static PlaceholderFragment newInstance(int sectionNumber) {
PlaceholderFragment fragment = new PlaceholderFragment();
Bundle args = new Bundle();
args.putInt(ARG_SECTION_NUMBER, sectionNumber);
fragment.setArguments(args);
return fragment;
}
public PlaceholderFragment() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
return rootView;
}
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
((MainActivity) activity).onSectionAttached(
getArguments().getInt(ARG_SECTION_NUMBER));
}
}
}
For those of you who still don't understand after reading #meda answer, here is my concise and complete explanation for this issue:
Let's say you have 2 Fragments, Fragment_A and Fragment_B which are auto-generated from the app. On the bottom part of your generated fragments, you're going to find this code:
public class Fragment_A extends Fragment {
//rest of the code is omitted
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(Uri uri);
}
}
public class Fragment_B extends Fragment {
//rest of the code is omitted
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
public void onFragmentInteraction(Uri uri);
}
}
To overcome the issue, you have to add onFragmentInteraction method into your activity, which in my case is named MainActivity2. After that, you need to implements all fragments in the MainActivity like this:
public class MainActivity2 extends ActionBarActivity
implements Fragment_A.OnFragmentInteractionListener,
Fragment_B.OnFragmentInteractionListener,
NavigationDrawerFragment.NavigationDrawerCallbacks {
//rest code is omitted
#Override
public void onFragmentInteraction(Uri uri){
//you can leave it empty
}
}
P.S.: In short, this method could be used for communicating between fragments. For those of you who want to know more about this method, please refer to this link.
Answers posted here did not help, but the following link did:
http://developer.android.com/training/basics/fragments/communicating.html
Define an Interface
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
#Override
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 = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
...
}
For example, the following method in the fragment is called when the user clicks on a list item. The fragment uses the callback interface to deliver the event to the parent activity.
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity
mCallback.onArticleSelected(position);
}
Implement the Interface
For example, the following activity implements the interface from the above example.
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
}
Update for API 23: 8/31/2015
Overrided method onAttach(Activity activity) is now deprecated in android.app.Fragment, code should be upgraded to onAttach(Context context)
#Override
public void onAttach(Context context) {
super.onAttach(context);
}
#Override
public void onStart() {
super.onStart();
try {
mListener = (OnFragmentInteractionListener) getActivity();
} catch (ClassCastException e) {
throw new ClassCastException(getActivity().toString()
+ " must implement OnFragmentInteractionListener");
}
}
See your auto-generated Fragment created by Android Studio. When you created the new Fragment, Studio stubbed a bunch of code for you. At the bottom of the auto-generated template there is an inner interface definition called OnFragmentInteractionListener. Your Activity needs to implement this interface. This is the recommended pattern for your Fragment to notify your Activity of events so it can then take appropriate action, such as load another Fragment. See this page for details, look for the "Creating event callbacks for the Activity" section: http://developer.android.com/guide/components/fragments.html
For those of you who visit this page looking for further clarification on this error, in my case the activity making the call to the fragment needed to have 2 implements in this case, like this:
public class MyActivity extends Activity implements
MyFragment.OnFragmentInteractionListener,
NavigationDrawerFragment.NaviationDrawerCallbacks {
...// rest of the code
}
You should try removing the following code from your fragments
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
The interface/listener is a default created so that your activity and fragments can communicate easier
In addition to #user26409021 's answer, If you have added a ItemFragment, The message in the ItemFragment is;
Activities containing this fragment MUST implement the {#link OnListFragmentInteractionListener} interface.
And You should add in your activity;
public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, ItemFragment.OnListFragmentInteractionListener {
//the code is omitted
public void onListFragmentInteraction(DummyContent.DummyItem uri){
//you can leave it empty
}
Here the dummy item is what you have on the bottom of your ItemFragment
With me it worked delete this code:
#Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnFragmentInteractionListener) {
mListener = (OnFragmentInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
Ending like this:
#Override
public void onAttach(Context context) {
super.onAttach(context);
}
OnFragmentInteractionListener is the default implementation for handling fragment to activity communication. This can be implemented based on your needs. Suppose if you need a function in your activity to be executed during a particular action within your fragment, you may make use of this callback method. If you don't need to have this interaction between your hosting activity and fragment, you may remove this implementation.
In short you should implement the listener in your fragment hosting activity if you need the fragment-activity interaction like this
public class MainActivity extends Activity implements
YourFragment.OnFragmentInteractionListener {..}
and your fragment should have it defined like this
public interface OnFragmentInteractionListener {
// TODO: Update argument type and name
void onFragmentInteraction(Uri uri);
}
also provide definition for void onFragmentInteraction(Uri uri); in your activity
or else just remove the listener initialisation from your fragment's onAttach if you dont have any fragment-activity interaction
Instead of Activity use context.It works for me.
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
mListener = (OnFragmentInteractionListener) context;
} catch (ClassCastException e) {
throw new ClassCastException(context.toString()
+ " must implement OnFragmentInteractionListener");
}
}
Just an addendum:
OnFragmentInteractionListener handle communication between Activity and Fragment using an interface (OnFragmentInteractionListener) and is created by default by Android Studio, but if you dont need to communicate with your activity, you can just get ride of it.
The goal is that you can attach your fragment to multiple activities and still reuse the same communication approach (Every activity could have its own OnFragmentInteractionListener for each fragment).
But and if im sure my fragment will be attached to only one type of activity and i want to communicate with that activity?
Then, if you dont want to use OnFragmentInteractionListener because of its verbosity, you can access your activity methods using:
((MyActivityClass) getActivity()).someMethod()
Just go to your fragment Activity and remove all method.....instead on on createview method.
your fragment has only on method oncreateview that's it.
//only this method implement other method delete
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_main, container, false);
return rootView;
}
and make sure your layout it is demo for u.
I'd like to add the destruction of the listener when the fragment is detached from the activity or destroyed.
#Override
public void onDetach() {
super.onDetach();
mListener = null;
}
and when using the new onStart() method with Context
#Override
public void onDestroy() {
super.onDestroy();
mListener = null;
}
Related
im a new guy on this programming world so i guess this question is simple. So I have one imagebutton on my fragment and I whatI want is that when I click on it, it does a fragment transaction to another fragment; but the thing is that when I run the app and click on the imagebutton, it only "superimposes" the content of the other fragment into the one where the imagebutton is, of course what i want to do is just to go to the other fragment, ive been dealing with this for a while so I hope someone helps me, thanks.
Here is my java code of the fragment
public class inicio extends Fragment {
public inicio() {
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_inicio,container,false);
ImageButton luces = (ImageButton)view.findViewById(R.id.imageButton);
luces.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
interior interior= new interior();
FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.replace(R.id.inicioo, interior).commit();
}
});
}
}
New code added...
public class transaction extends MainActivity
implements com.example.administradora.prueba.inicio.OnSelectedListener{
public void onButtonSelected() {
interior interior= new interior();
getSupportFragmentManager().beginTransaction().replace(R.id.inicioo, interior).commit();
}
}
but i get this error in the logcat:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.administradora.prueba/com.example.administradora.prueba.MainActivity}: java.lang.ClassCastException: com.example.administradora.prueba.MainActivity#20f8fe9b must implement OnSelectedListener
You shouldn't replace a Fragment from another Fragment. Try to do that through the Activity.
Define some interface for the Fragment to hold
public class MyFragment extends Fragment {
OnSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnSelectedListener {
public void onButtonSelected();
}
#Override
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 = (OnSelectedListener ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnSelectedListener");
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view=inflater.inflate(R.layout.fragment_inicio,container,false);
ImageButton luces = (ImageButton)view.findViewById(R.id.imageButton);
luces.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View view) {
// Send the event to the host activity
if (mCallback != null) mCallback.onButtonSelected();
}
});
return view;
}
}
And swap the Fragment container in the interface implementation from the Activity.
public class MainActivity extends Activity
implements MyFragment.OnSelectedListener{
...
public void onButtonSelected() {
interior interior= new interior();
getSupportFragmentManager()
.beginTransaction()
.replace(R.id.inicioo, interior)
.commit();
}
}
The first two sections of Communicating with Other Fragments is what you are looking for.
Have two fragments A and B, Fragment A has Textview and Fragment B has edittext and button.
Click on submit in FragmentB need to update textview in FragmentA with Edittext text.
How to do communication between fragment?
n this example, FragmentA call notify.
INotifier
public interface INotifier {
public void notify(Object data);
}
Utils
public class Utils {
public static INotifier notifier;
}
FragmentA
public FragmentA extends Fragment {
public void onCreateView(...) {
}
public void inSomeMethod() {
if (Utils.notifier != null) {
Utils.notifier.notify(data);
}
}
}
FragmentB
public FragmentB extends Fragment implements INotifier {
public void onCreateView(...) {
Utils.notifier = this;
}
#Override
public void notify(Object data) {
// handle data
}
}
You need to interact the activity first which will interact the second fragment. and also read this article on how to do it.
https://developer.android.com/guide/components/fragments.html#CommunicatingWithActivity
The communication between Fragments is done usinng Listeners. When you want to update fragment, use the listener to tell the MainActivity to update the second fragment as recommended by Google http://developer.android.com/training/basics/fragments/communicating.html. Create the interface in Fragment and Implement this in Activity
Listener in Fragment
public interface FragmentUpdateInterface {
void updateFragment(String newText);
}
#Override
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 = (FragmentUpdateInterface ) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement FragmentUpdateListener");
}
}
#Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Send the event to the host activity
mCallback.updateFragment("New Text");
}
MainActivity
Implement fragment in MainActivity as
public static class MainActivity extends Activity
implements MyFragment.FragmentUpdateListener{
public void updateFragment(String newText) {
OtherFragment otherFrag = (OtherFragment)
getSupportFragmentManager().findFragmentById(R.id.other_fragment);
if (otherFrag != null) {
otherFrag.updateFragment(newText);
} else {
// Otherwise, we're in the one-pane layout and must swap frags...
// Create fragment and give it an argument for the selected article
OtherFragment otherFrag = new OtherFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
otherFrag.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
transaction.replace(R.id.fragment_container, otherFrag);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
}
Hope this helps.
UPDATE:
You can also use LocalBroadcastManager.getInstance().sendBroadcast() to notify to the other fragment as well.
I have the following situation:
I have an Activity that hosts a ViewPager, and I have 4 Fragments;
the ViewPager at the beginning contains Fragment A,
when the user swipes on the ViewPager Fragment B goes into the ViewPager, then Fragment C and Fragment D ...etc...
Now as soon as the FragmentPagerAdapter is instantiated at least 2 of the Fragments are created.
This poses 2 problem:
Every Fragment needs to perform network calls, but I do not want to do unnecessary ones (I do not want to make network calls for Fragment B, if the user never swipes to Fragment B );
similar to 1.), I need to show a ProgessDialog when a Fragment perform network calls, but I do not want to show dialogs from Fragment B if the user never goes to it...
Please what kind of pattern should I use in such a circumstance?
Activity
public class PagerActivity extends ActionBarActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.viewpager_layout);
ViewPager pager=(ViewPager)findViewById(R.id.pager);
TabPageIndicator tabs=(TabPageIndicator)findViewById(R.id.titles);
pager.setAdapter(buildAdapter());
tabs.setViewPager(pager);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
FragmentPagerAdapter
public class MyFragmentPagerAdapter extends FragmentPagerAdapter {
#Override
public int getCount() {
return (4);
}
#Override
public Fragment getItem(int position) {
if (position == 1) {
if (dashbardFragment == null)
dashbardFragment = DashBoardFragment.newInstance(position);
return dashbardFragment;
}
if (position == 0) {
if (listOfParticipantFragment == null)
listOfParticipantFragment = ListOfParicipantsFragment
.newInstance(position);
return listOfParticipantFragment;
}
}
1 Fragment
public class ListOfParicipantsFragment extends Fragment {
public static ListOfParicipantsFragment newInstance(int position) {
ListOfParicipantsFragment frag = new ListOfParicipantsFragment();
return (frag);
}
public static String getTitle(Context ctxt, int position) {
return myApplication.getContext().getResources().getString(R.string.list_of_participants_fragment_title);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View result = inflater.inflate(R.layout.guest_list_fragment_layout,
container, false);
return (result);
}
Try this, in each fragment override below method and call your function when it is visible:
#Override
public void setUserVisibleHint(boolean isVisibleToUser) {
super.setUserVisibleHint(isVisibleToUser);
if(isVisible()){
if(isVisibleToUser){
Log.d("MyTag","My Fragment is visible");
}else{
Log.d("MyTag","My Fragment is not visible");
}
}
}
EDIT
Note: This is only useful when using a FragmentPagerAdapter or FragmentStatePagerAdapter
Basically what you want to do, is, find which fragment is currently being viewed when you swipe. And then, do your network calls.
You can take advantage of the ViewPager listeners to get notified when the user swipes to a new page. Docs : http://developer.android.com/reference/android/support/v4/view/ViewPager.OnPageChangeListener.html#onPageSelected(int)
This will give you the position of the View. But i'm assuming that what you want is the actual fragment, which is a bit more tricky.
But, this has been answered already in here : Is it possible to access the current Fragment being viewed by a ViewPager?
Hope it helps
Let me introduce my idea to you:
getCurrentItem()
Method of ViewPager
getItem(int position)
Method of FragmentPagerAdapter Return the Fragment associated with a specified position.
You can define an Interface holding the method for Network I/O like
public Interface INetworkOnFragment{
void handle(){
//...
}
}
And implement it on your fragments and handle their own business logic (Network calls).
In main Activity ,set ViewPager.OnPageChangeListener on ViewPager object like here:
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener(){
public void onPageScrollStateChanged(int state){
//donothing
}
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels){
//donothing
}
public void onPageSelected(int position){
INetworkOnFragment interface =(INetworkOnFragment) (pager.getAdapter().getItem(position));//get the current fragment and call handle method on it,dont need to care about whichever fragment it is .
interface.handle()
}
});
The most important is onPageSelected(int position),Inside the callback it get the current fragment and call handle method on it,dont need to care about whichever fragment it is .
Remember the handle method are called in Activity and not in fragments.All the Network calls are implemention of Interface,which make it easy to deal in Activity.
Check out the solution from my answer here
1) Create LifecycleManager Interface The interface will have two methods (onPauseFragment and onResumeFragment) and each ViewPager’s Fragment will implement it
2) Let each Fragment implement the interface
3) Implement interface methods in each fragment - start your AsyncTask in onResumeFragment
4) Call interface methods on ViewPager page change You can set OnPageChangeListener on ViewPager and get callback each time when ViewPager shows another page
5) Implement OnPageChangeListener to call your custom Lifecycle methods
Create a page into view method for FragmentStatePagerAdapter which calls a method on the fragment when the fragment comes into view.
Implement the OnPageIntoView interface in your fragment.
public class SomethingDifferent extends Fragment implements OnPageIntoView {
...
/*
* Called when this page comes into view
*
* #see com.gosylvester.bestrides.SettingFragmentPagerSupport.MyAdapter.
* OnPageIntoView#onPageIntoView()
*/
#Override
public void onPageIntoView() {
// this is just some random example code
// that does some heavy lifting it only runs when the fragment
// frist comes into view
if (fragmentActivity != null) {
if (lrc == null) {
lrc = new ClientServiceLocationRecorder(
new WeakReference<Context>(
fragmentActivity.getApplicationContext()),
lrcCallback);
}
// get a status message from the location recorder
lrc.sndMessageToLocationRecorder(ServiceLocationRecorder.MSG_RECORD_STATUS);
}
}
Create a custom FragmentStatePagerAdapter Override the setPrimaryItem method and if the object can be cast to the interface then call through the interface one time only.
public static class MyAdapter extends FragmentStatePagerAdapter {
public interface OnPageIntoView {
public void onPageIntoView();
}
private Fragment mCurrentFragment;
//bonus method to get the current fragment
public Fragment getCurrentFragment() {
return mCurrentFragment;
}
static int lastPosition = -1;
#Override
public void setPrimaryItem(ViewGroup container, int position,
Object object) {
//quickly determine if the primary item has changed
//and one time only call through interface
if (position != lastPosition) {
lastPosition = position;
//determine if this is fragment it should be but lets avoid
//class cast exceptions
if (Fragment.class.isAssignableFrom(object.getClass())) {
mCurrentFragment = ((Fragment) object);
//determine if the onPageIntoView interface has
//been implemented in the fragment
//if so call the onPageIntoView
if (OnPageIntoView.class.isAssignableFrom(mCurrentFragment
.getClass())) {
((OnPageIntoView) mCurrentFragment).onPageIntoView();
}
}
}
super.setPrimaryItem(container, position, object);
}
}
It seems easy to me,what you need is Fragments onResume() method. This will be called only when your fragment is VISIBLE to user.
Here you can add logic to initiate your network call. It guarantees that your fragment is in visible mode.
See this
However you can optimize your network calls logic, using LoaderManager with AsyncTaskLoader pattern.
Loaders take care of screen orientation changes & they cache data for you. So that network call is not initiated twice for same operation.
From Android documentation
Introduced in Android 3.0, loaders make it easy to asynchronously load
data in an activity or fragment. Loaders have these characteristics:
They are available to every Activity and Fragment.
They provide asynchronous loading of data.
They monitor the source of their data and deliver new results
when the content changes.
They automatically reconnect to the last loader's cursor when
being recreated after a configuration change.
Thus, they don't need to re-query their data.
You can use any Asynchronous HTTP lib for network calls like Retrofit
i found one tutorial for AsyncTaskLoader & LoaderManager # this link, below are some quotes from tutorial
Loaders aren't trivial, so why use them in the first place? Well, in
most cases, you would use them in the same scenarios where you've been
using AsyncTasks; in fact, some loader subclasses extend AsyncTask.
Just as AsyncTasks are used to perform any long-running operation that
would tie up the user thread and ultimately throw the dreaded
Application Not Responding (ANR), loaders perform in the same manner
with the same purpose. The main difference is loaders are specialized
for loading data. As such, loaders offer a number of efficiency and
convenience benefits.
#Andrew Carl provide good idea. I also use the similar approach in my projects. I think it's more generalized.
Create an interface:
public interface ViewPagerFragment {
void onSelected();
void onDeselected();
}
And this common helper:
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.view.ViewPager;
public class ViewPagerHelper implements ViewPager.OnPageChangeListener {
private final FragmentManager mFragmentManager;
private final ViewPager mViewPager;
private int mSelectedPage;
public ViewPagerHelper(FragmentManager fragmentManager, ViewPager viewPager) {
mFragmentManager = fragmentManager;
mViewPager = viewPager;
mSelectedPage = -1;
}
#Override
public void onPageSelected(int position) {
Fragment previous = findViewPagerChildFragment(mFragmentManager, mViewPager, mSelectedPage);
if (previous instanceof ViewPagerFragment) {
((ViewPagerFragment) previous).onDeselected();
}
Fragment current = findViewPagerChildFragment(mFragmentManager, mViewPager, position);
if (current instanceof ViewPagerFragment) {
((ViewPagerFragment) current).onSelected();
}
mSelectedPage = position;
}
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
// empty
}
#Override
public void onPageScrollStateChanged(int state) {
// empty
}
public static Fragment findViewPagerChildFragment(FragmentManager manager, ViewPager pager, int position) {
if (pager == null) {
return null;
}
String tag = "android:switcher:" + pager.getId() + ":" + position;
return manager.findFragmentByTag(tag);
}
}
Now you may use them for any purpose:
Fragment:
import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast;
public class MyFragment extends Fragment implements ViewPagerFragment {
private boolean mSelected;
public static MyFragment newInstance(int position) {
Bundle args = new Bundle();
args.putInt("position", position);
MyFragment result = new MyFragment();
result.setArguments(args);
return result;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
TextView result = new TextView(inflater.getContext());
result.setText("Position: " + getPosition());
return result;
}
private int getPosition() {
return getArguments().getInt("position");
}
#Override
public void onSelected() {
mSelected = true;
start();
}
#Override
public void onDeselected() {
mSelected = false;
}
private void start() {
final Handler handler = new Handler();
handler.postDelayed(new Runnable() {
#Override
public void run() {
Activity activity = getActivity();
if (activity == null) {
return;
}
if (!mSelected) {
Toast.makeText(activity, "Fragment #" + getPosition() + " stopped", Toast.LENGTH_SHORT).show();
return;
}
TextView textView = (TextView) activity.findViewById(R.id.text);
if (textView != null) {
textView.setText("Fragment #" + getPosition() + " works: " + System.nanoTime() % 10000);
}
handler.postDelayed(this, 150);
}
}, 150);
}
}
Activity:
import android.os.Bundle;
import android.support.v4.view.ViewPager;
import android.support.v7.app.ActionBarActivity;
public class MainActivity extends ActionBarActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ViewPager viewPager = (ViewPager) findViewById(R.id.view_pager);
viewPager.setOnPageChangeListener(new ViewPagerHelper(getSupportFragmentManager(), viewPager));
viewPager.setAdapter(new MyAdapter(getSupportFragmentManager()));
}
}
Adapter:
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
public class MyAdapter extends FragmentPagerAdapter {
public MyAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return MyFragment.newInstance(position);
}
#Override
public int getCount() {
return 42;
}
}
Check complete demo on github.
I'm trying to update an object from a fragment contained within a swipe view. The code I have is taken directly from the Android documentation. What I want to do is pass an object from the main CollectionDemoActivity down into the DemoObjectFragment fragment, update it using a button in that fragment and then pass it back up to the main activity. What's the best way to accomplish this?
I've tried passing the object in a bundle as a serialisable through the DemoCollectionPagerAdapter and then again down to the fragment but this seems really cumbersome. I've also tried declaring the object in the main activity and just referencing it in the fragment class but I get complaints that it can't have a non-static reference in a static context.
public class CollectionDemoActivity extends FragmentActivity {
// When requested, this adapter returns a DemoObjectFragment,
// representing an object in the collection.
DemoCollectionPagerAdapter mDemoCollectionPagerAdapter;
ViewPager mViewPager;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_collection_demo);
// ViewPager and its adapters use support library
// fragments, so use getSupportFragmentManager.
mDemoCollectionPagerAdapter =
new DemoCollectionPagerAdapter(
getSupportFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.pager);
mViewPager.setAdapter(mDemoCollectionPagerAdapter);
}
}
// Since this is an object collection, use a FragmentStatePagerAdapter,
// and NOT a FragmentPagerAdapter.
public class DemoCollectionPagerAdapter extends FragmentStatePagerAdapter {
public DemoCollectionPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int i) {
Fragment fragment = new DemoObjectFragment();
Bundle args = new Bundle();
// Our object is just an integer :-P
args.putInt(DemoObjectFragment.ARG_OBJECT, i + 1);
fragment.setArguments(args);
return fragment;
}
#Override
public int getCount() {
return 100;
}
#Override
public CharSequence getPageTitle(int position) {
return "OBJECT " + (position + 1);
}
}
// Instances of this class are fragments representing a single
// object in our collection.
public static class DemoObjectFragment extends Fragment {
public static final String ARG_OBJECT = "object";
#Override
public View onCreateView(LayoutInflater inflater,
ViewGroup container, Bundle savedInstanceState) {
// The last two arguments ensure LayoutParams are inflated
// properly.
View rootView = inflater.inflate(
R.layout.fragment_collection_object, container, false);
Bundle args = getArguments();
((TextView) rootView.findViewById(android.R.id.text1)).setText(
Integer.toString(args.getInt(ARG_OBJECT)));
return rootView;
}
}
So after a lot of searching and reading I found a nice solution that works for me. For those interested I created an interface in the fragment class that is implemented in the Main activity. The methods were kicked off through a button press in the fragment class. This way I was able to pass variables up to the main class without ever needing to pass the entire object down to the fragment.
So my classes were mostly the same with these bits added:
And the fragment class which contains the interface. The onAttach() method needs to be called which gets a reference to the activity that the fragment will be attached to. This activity reference is binded to an instance of the interface in the fragment.
public class DemoObjectFragment extends Fragment {
....
//Creating the interface
public interface ButtonListener {
//This method will be called in the main activity. Whatever is passed in as the parameter can be used by the main activity
public void ButtonPressed(int myInt);
}
//Getting an instance of the interface
ButtonListener updateListener;
//Getting a reference to the main activity when the fragment is attached to it.
//The activity reference is bound to the instance of the interface.
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
// Ensures the activity implements the callback interface
try {
updateListener = (DayUpdateButtonListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString());
}
}
....
//On the button click call the method through the activity reference from the onAttach() method
//Creating an int object to pass into the method.
int myNewInt = 5;
myButton.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
updateListener.ButtonPressed(myNewInt);
}
});
}
Finally in the main activity simply implement the interface and add the method from it.
public class CollectionDemoActivity extends FragmentActivity implements DemoObjectFragment.ButtonListener {
....
#Override
public void ButtonPressed(int myInt) {
//Update the object with myInt
}
}
Taking the default 'Master/Detail' flow template in Eclipse, and adding a third Fragment (let's call it Edit, launched from the pre-existing Detail Fragment) and I'm now looking to open the Edit Fragment when a user clicks on an item in the Detail Fragment.
I've implemented an interface on the Detail fragment, however depending on whether the application is on a Tablet or Phone (dual-pane or not), the Iterface requires to be implemented by either the Detail Activity, or the Main Activity in order to function. I assume this is due to the way that the template implements opening of the Detail Fragment as an activity when the device is not in dual-pane mode.
Have I implemented this incorrectly, or is there a best practice that would allow me to unify the implementation of the interfaces into the main activity?
Here are some reduced snippets from the Master and Detail Fragments, showing the requirement for dual-implementation of the Detail Fragments interface.
Code for WaveListWactivity.java (first Fragment)
public class WaveListActivity extends FragmentActivity implements
WaveListFragment.Callbacks,WaveDetailFragment.Callbacks {
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wave_list);
if (findViewById(R.id.wave_detail_container) != null) {
mTwoPane = true;
((WaveListFragment) getSupportFragmentManager().findFragmentById(
R.id.wave_list)).setActivateOnItemClick(true);
}
}
//Interface from WaveListFragment
#Override
public void onWaveSelected(int id) {
if (mTwoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.wave_detail_container, new WaveDetailFragment()).commit();
} else {
Intent detailIntent = new Intent(this, WaveDetailActivity.class);
startActivity(detailIntent);
}
}
//Interface from WaveDetailFragment
#Override
public void onItemSelected(int id) {
if (mTwoPane) {
getSupportFragmentManager().beginTransaction()
.replace(R.id.wave_detail_container, new WaveEditFragment()).addToBackStack(null).commit();
} else {
Intent detailIntent = new Intent(this, WaveDetailActivity.class);
startActivity(detailIntent);
}
}
}
Code for WaveDetailActivity.java (second Fragment)
public class WaveDetailActivity extends FragmentActivity implements WaveDetailFragment.Callbacks {
private boolean mTwoPane;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_wave_detail);
if (findViewById(R.id.wave_detail_container) != null) {
mTwoPane = true;
}
if (savedInstanceState == null) {
getSupportFragmentManager().beginTransaction().add(R.id.wave_detail_container, new WaveDetailFragment()).commit();
}
}
//Callback from WaveDetailFragment
#Override
public void onItemSelected(int id) {
if (mTwoPane) {
getSupportFragmentManager().beginTransaction().replace(R.id.wave_detail_container, new WaveEditFragment()).addToBackStack(null).commit();
} else {
Intent detailIntent = new Intent(this, WaveEditActivity.class);
startActivity(detailIntent);
}
}
}
I think you may be slightly confused as to the difference between FragmentActivity, Fragments and the callback interfaces that you need to implement on your Activity. From the looks of it, all the code snippets are Activity classes and not Fragments. I would expect a Fragment to look something like:
/** From http://developer.android.com/training/basics/fragments/communicating.html */
public class HeadlinesFragment extends ListFragment {
OnHeadlineSelectedListener mCallback;
// Container Activity must implement this interface
public interface OnHeadlineSelectedListener {
public void onArticleSelected(int position);
}
#Override
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 = (OnHeadlineSelectedListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnHeadlineSelectedListener");
}
}
...
}
And then your Activity:
public static class MainActivity extends Activity
implements HeadlinesFragment.OnHeadlineSelectedListener{
...
public void onArticleSelected(int position) {
// The user selected the headline of an article from the HeadlinesFragment
// Do something here to display that article
}
}
The overall 'flow' of this should be:
Fragments act as blobs of UI code with callbacks for all the interesting stuff.
Callbacks implemented by Activity.
The overall number of actions you can do depends on the fragments which depends on the size of your screen (so you may have 2 fragments which feed into a single Activity). The logic for determining whether to show one or two fragments should be done in the Activity.