I read Android documentation on how to add a Fragment to an activty. It says that to add a Fragment to an Activty I should write this code inside the Activity class:
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
but this is for android.app.Fragment class.
For android.support.v4.Fragment, documentation say that instead of getFragmentManager() I should call getSupportFragmentManager() and that Activity must extends FragmentActivty.
So I did this change, and now this is my activity code:
public class ExampleActivity extends AppCompactActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_fragment);
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction =
fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
}
}
AppCompactActivity is a subclass of FragmentActivity so I respected the constraints.
The problem is that if I run my application I get this error:
java.lang.RuntimeException:
com.myapplication.ExampleActivity#13388c6 must implement OnFragmentInteractionListener
I typed OnFragmentInteractionListener on Android doc web search and this is what I get:
Immediately below there is the link
https://developer.android.com/training/basics/fragments/communicating.html
that shows me a guide to communication between fragments. It speaks about ListFragment and I don't care it.
I'm very very confuse because every time I read documentation there are always things that force me to make internet search to find workaround or fix to problems.
Is there a persone that can explain me first how to fix this problem.
From the tutorial, you can read the following:
In order to receive event callbacks from the fragment, the activity
that hosts it must implement the interface defined in the fragment
class.
So, implement it in your Activity with something like this in your fragment:
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
}
}
Why did you need it? Because you've forced the host activity to implement it with something like this:
#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");
}
}
Related
I have a Main Activity with a bunch of Fragments connected to it.
One of the fragments has to be able to recieve data from MainActivity after it has been loaded in the FragmentTransaction and committed.
What is the best way to call a method in that specific fragment?
Do I have to implement a Interface and include it in the MainActivty just for this one fragment? Is there a better way? Can someone point me in the correct direction?
What I have tried now : (Failing at settings ContactsInterface in the MainActivity)
ContactsInterface
public interface ContactsInterface {
void notifyDenied();
void notifyGranted();
}
Fragment
Class.... implements ContactsInterface...
#Override
public void notifyDenied() {
Log.d("DENIED", "DENIED CALLBACK");
}
#Override
public void notifyGranted() {
Log.d("GRANTED", "GRANTED CALLBACK");
}
Main Activity
try {
contactsInterface = (ContactsInterface) this.getApplicationContext();
} catch (ClassCastException e) {
throw new ClassCastException(this.toString()
+ " Needs to implement the methods");
}
Last example throws an ClassCastException.
You need to cast the fragment itself not the Application Context.
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
ExampleFragment fragment = new ExampleFragment();
fragmentTransaction.add(R.id.fragment_container, fragment);
fragmentTransaction.commit();
ContactsInterface contactsInterface = (ContactsInterface) fragment;
//contactsInterface.notifyGranted();
//contactsInterface.notifyDenied();
You can get all the active fragments in the FragmentManager and filter for the ones that have the required interface:
supportFragmentManager.fragments
.map { it as? ContactsInterface }
.filterNotNull()
.forEach {
it.notifyDenied()
}
or if you need to use Java:
List<Fragment> fragments = getSupportFragmentManager().getFragments();
for(fragment : fragments) {
if(fragment instanceof ContactsInterface) {
((ContactsInterface)fragment).notifyDenied();
}
}
You may want to also fail (hard or soft) if no fragments are found.
The advantage of doing this is that you don't care how the fragment was started (manually or by layout) and also don't have to care about reconnecting when the activity restarts for example.
This library may be useful to you.
You should do something like this in your fragment:
#Override
public void onCreateView() {
super.onStart();
EventBus.getDefault().register(this);
}
#Override
public void onDestroyView() {
super.onStop();
EventBus.getDefault().unregister(this);
}
#Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
MessageEvent is just an example, you can use whatever structure you want.
in the Activity you post messages like this:
EventBus.getDefault().post(new MessageEvent());
Check the documentation of the library to have a better idea and use a better approach to your case.
you can use onCreate() and onDestroy() instead. That depends on your logic and you you are updating the view of the fragment when you receive new messages from the Activity.
Happy coding :).
Here is my main activity. I followed this guide about Fragments correctly. When I click "Back" button, my application is closed instead of returning to the MainScreenFragment. Why is this happening and why addToBackStack() doesn't work?
public class MainScreenActivity extends ActionBarActivity implements MainScreenFragment.OnFrameChoiced {
private MainScreenFragment mainScreenFragment;
private AddWordsFragment addWordsFragment;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_screen);
mainScreenFragment = new MainScreenFragment();
addWordsFragment = new AddWordsFragment();
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.add(R.id.container, mainScreenFragment).addToBackStack(null).commit();
}
#Override
public void choiceFrame(int id) {
switch (id) {
case R.id.add_new_words_frame:
FragmentManager fm = getFragmentManager();
FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.container, addWordsFragment).addToBackStack(null).commit();
fm.executePendingTransactions();
break;
}
}
P.S. I tried to use a solution from this topic, but It still doesn't work.
did you try overriding the back like below:
#overide
public void onBackPressed(){
FragmentManager fm = getSupportFragmentManager();
if (fm.getBackStackEntryCount() > 1) {
fm.popBackStack();
} else {
finish();
}
}
(I know you have picked up your desirable answer, but I have found a little more against this problem)
Though as Android official site has documented:
By calling addToBackStack(), the replace transaction is saved to the back stack so the user can reverse the transaction and bring back the previous fragment by pressing the Backbutton.
But as a matter of fact, this is in a precondition that you are using the standard android activity, specifically, the android.app.Activity. Because this methods in android.app.Activity will work when Backbutton is pressed:
public void onBackPressed() {
if (mActionBar != null && mActionBar.collapseActionView()) {
return;
}
if (!mFragments.getFragmentManager().popBackStackImmediate()) {
finishAfterTransition();
}
}
But, if you are extending your xxxxActivity from someone else, for example, the AppCompatActivity, FragmentActivity, ActionBarActivity, it will be another story, because in FragmentActivity, onBackPressed() method is totally overrode:
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
supportFinishAfterTransition();
}
}
Note that mFragments.getFragmentManager() is replaced by mFragments.getSupportFragmentManager(), so in cases like this, you should begin your FragmentTransaction using getSupportFragmentManager() of the Activity. and as a consequence of that, you don't have to override onBackPressed method in your Activity.
BTW, ActionBarActivity extends AppCompatActivity extends FragmentActivity,they all come from the support library, you know what I mean, remember to use getSupportFragmentManager() instead of getFragmentManager() when using support library in order to get the compatible behavior.
I have a Class1 extending ListFragment1.
I have a Class2 extending ListFragment2.
I'm currently passing an Intent like this inside OnItemClickListener.
Intent intent = new Intent(getActivity().getApplicationContext(), ListFragment2.class);
intent.putExtra("id", "1");
getActivity().startActivity(intent);
}
However, i get this exception:
java.lang.ClassCastException: com.abc.xyz.Class2 cannot be cast to android.app.Activity
Please help ! :(
First you should know:
You are calling startActivity(intent) and your Intent is pointing to a Fragment.
You cannot pass intents to your Fragment like that since Fragments are not App Components.
Communcation between Fragments should be between the Activity holding them.
Best Solution:
Pass your data from Class1 to your Activity and then send it to Class2, this link provides you exactly this, no more or less code:
http://developer.android.com/training/basics/fragments/communicating.html
It's a must read when you want to communicate between fragments.
Intent must point towards a class extending android.app.Activity which is not a case with your code.
Instead of extending ListFragment2 externally, you can make an inner class extending ListFragment2 and pass the value.
Hope this helps.
Both the fragments must communicate with the activity for communication between each other.The activity acts as a sort of a mediater between the fragement.The flow will look like this:
Fragment1 passes the intent to Activity1 with certain parameters which helps Activity1 to identify that it needs to pass this intent to Fragment2.
Activity1 on receiving the intent, passes this intent to Fragment 2.
How the fragment communicates with activity can be designed in following way mentioned here
To allow a Fragment to communicate up to its Activity, you can define an interface in the Fragment class and implement it within the Activity. The Fragment captures the interface implementation during its onAttach() lifecycle method and can then call the Interface methods in order to communicate with the Activity.
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");
}
}
...
}
Now the fragment can deliver messages to the activity by calling the onArticleSelected() method (or other methods in the interface) using the mCallback instance of the OnHeadlineSelectedListener interface.
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
In order to receive event callbacks from the fragment, the activity that hosts it must implement the interface defined in the fragment class.
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
}
}
Deliver a Message to a Fragment
The host activity can deliver messages to a fragment by capturing the Fragment instance with findFragmentById(), then directly call the fragment's public methods.
For instance, imagine that the activity shown above may contain another fragment that's used to display the item specified by the data returned in the above callback method. In this case, the activity can pass the information received in the callback method to the other fragment that will display the item:
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
ArticleFragment articleFrag = (ArticleFragment)
getSupportFragmentManager().findFragmentById(R.id.article_fragment);
if (articleFrag != null) {
// If article frag is available, we're in two-pane layout...
// Call a method in the ArticleFragment to update its content
articleFrag.updateArticleView(position);
} else {
// Otherwise, we're in the one-pane layout and must swap frags...
// Create fragment and give it an argument for the selected article
ArticleFragment newFragment = new ArticleFragment();
Bundle args = new Bundle();
args.putInt(ArticleFragment.ARG_POSITION, position);
newFragment.setArguments(args);
FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
// Replace whatever is in the fragment_container view with this fragment,
// and add the transaction to the back stack so the user can navigate back
transaction.replace(R.id.fragment_container, newFragment);
transaction.addToBackStack(null);
// Commit the transaction
transaction.commit();
}
}
}
This is one of the way to achieve it, i use constructor to pass the data
FragmentOne.java
int myData=12;
FragmentManager manager = getActivity().getSupportFragmentManager();
Fragment frgObj=FragmentTwo.newInstance(myData);
FragmentTransaction ft = manager.beginTransaction();
ft.replace(R.id.container, frgObj,"FragmentTwo");
ft.addToBackStack(null);
ft.commit();
FragmentTwo.java
int myData;
public static FragmentTwo newInstance(int _myData){
FragmentTwo fragment = new FragmentTwo();
myData=_myData
return fragment;
}
ALSO REFER -- this -- StackOVERFLOW POST
I've a activity which basically is :
public class FragmentContainer extends FragmentActivityBase implements IRefreshListener {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getIntent().getExtras() == null
|| getIntent().getExtras().get("type") == null) {
showProductList();
}
else
{
if (getIntent().getExtras().get("type").equals("customer"))
showCustomerList();
}
#Override
public void showProductList() {
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager
.beginTransaction();
// load the product list
ProductList fragment = new ProductList();
fragmentTransaction.replace(R.id.fragment_container, fragment)
.addToBackStack(null);
fragmentTransaction.commit();
}
.....
}
in the fragment, I use onCreateView to get intent and then I create my view.
If I need to change the fragment, I get the reference to the parent Activity (taken from onAttach) and I call method referenced by the IRefreshListener.
like :
IRefreshListener mCallback;
#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 = (IRefreshListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement IRefreshListener");
}
}
public void callCustomer() {
mCallback.showCustomerList();
}
It works but whne I change the orientation, even I use setRetainInstance(true) it will be reseted.
I have 2 questions :
Do I use the good pattern to manage my application. The big activity which contains one fragment become bigger with the time
How should I handle orientation change ?
Regards
I do not find this pattern is more perfect or best one, although it is or was a suggestion from Google. Because it could be a worse coding style if fragment knows particular activity or listeners, you might write more and more code, when you wanna to let your fragment know more its "container" or "parents". Will the fragment later be used for other activity which has not been implemented with IRefreshListener etc, you will code much more.
My introduce is using Otto-Bus or Event-Bus. You can just send message from one to one. Every one doesn't have to know each other.
Working with Robolectric , I'm very new to android. I made a first test class using Activity. It worked nicely.
Now I want make a test for fragment.
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment(fragment);
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment(LoginFragment fragment) {
FragmentManager fragmentManager = new FragmentActivity().getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText)fragment.getActivity().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is my first test class for Fragment class. It throws
"java.lang.IllegalStateException: Activity has been destroyed" in startFragment#fragmentTransaction.commit().
Anyone knows how to fix this ?
You can find whole source from https://github.com/msbaek/frame-test
Thanks in advance !!
In my case, specifically, my problem was when creating the activity.
I was using
activity = Robolectric.buildActivity(MyActivity.class).get();
And it should be
activity = Robolectric.buildActivity(MyActivity.class).create().get();
Hope it helps someone :D
#RunWith(RobolectricTestRunner.class)
public class LoginFragmentTest {
private LoginFragment fragment;
#Before
public void setup() {
fragment = new LoginFragment();
startFragment();
assertThat(fragment, notNullValue());
assertThat(fragment.getActivity(), notNullValue());
}
private void startFragment() {
FragmentActivity activity = new FragmentActivity();
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
FragmentManager fragmentManager = activity.getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(fragment, null);
fragmentTransaction.commit();
}
#Test
public void login() {
EditText idEditText = (EditText) fragment.getView().findViewById(R.id.main_id);
assertThat(idEditText, notNullValue());
}
}
This is working version. Following 3 lines are important(it's from robolectric source - DialogFragmentTest).
shadowOf(activity).callOnCreate(null);
shadowOf(activity).callOnStart();
shadowOf(activity).callOnResume();
The fragments are supposed to be displayed from an Activity. The flow should be:
allocate a new fragment object in a FragmentActivity class
get the fragment manager to add the newly allocated fragment
In your case you do not have a connection to a real activity. You allocate a FragmentActivity with new FragmentActivity() and try to get the support manager. While this compiles there is no "real" activity able to manage your fragment. Fragments can be added on activities already displayed and here it's not the case.
I recommend reading this page as it explains these things very well: http://developer.android.com/guide/components/fragments.html
That happened for me when I used fragmentTransaction.commitAllowingStateLoss(); from sub Fragment whose parent fragment had setRetainInstance(true); I had activity as property what lead to leaking activity on rotation.