Pattern for Activity / Fragment in android - android

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.

Related

Android - Talk to an active Fragment from Main Activity

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 :).

Android - support.v4.Fragment

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");
}
}

Transitioning between Android Fragments using a Fragment controller

I am new to Android and I am trying to build an app containing three fragments: let's say A, B, and C. I want a button on A to show me B when clicked, and a button on B to show me C when clicked. I understand that one way is to use FragmentManager like this: in fragment A, I can have a button click listener that does
getSupportFragmentManager().beginTransaction()
.add(R.id.fragment_container, new B())
.commit();
However, I do not want A to know about fragment B, or B to know about C. I was thinking of creating some kind of FragmentController to solve this problem such that the Controller would know about the necessary transitions and maintain a state machine.
Is this a common pattern in Android? I tried googling but I didn't see many code examples for this pattern. How can I decouple the different fragments so that the fragments don't have to worry about the transitions?
Thanks.
Yes, it is a common pattern, you need understand how to work the comunnication between fragments.
Taking on, your fragments are contained in an activity, maybe you can do the next:
The Fragment listener
public interface FragmentNavigationListener {
public void onNavigateTo(int fragment);
}
The Activity which implements callback
public static final int Fragment FRAGMENT_A = 0;
public static final int Fragment FRAGMENT_B = 1;
#Override
public void onNavigateTo(int fragment){
switch fragment{
case FRAGMENT_A :
...
case FRAGMENT_B :
...
}
}
Each Fragment
must encore that parent activity implements the listener
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mCallback = (FragmentNavigationListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement FragmentNavigationListener");
}
}
In OnclickListener from each button in each fragment you call the listener.
button.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
// Perform action on click
mCallback.onNavigateTo(FRAGMENT_B);
}
});
Write fragmentInteraction interfaces, define methods in this interface, then onAttach of the fragment lifeCycle do this
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
try {
mListener = (OnFragmentInteractionListener) activity;
} catch (ClassCastException e) {
throw new ClassCastException(activity.toString()
+ " must implement OnFragmentInteractionListener");
}
}
That way every activity to which this fragment gets attached to must implement the OnFragmentInteractionListener. Now in your onClick method do mListener.onClick(view) assuming you have a method called onClick(View v) defined in your interface. And in your activity's implementation of the onClick function do the fragmentTransaction

Why on landscape/portrait orientation change fragments get attached before onCreate called on main activity?

I have main activity which embeds fragment:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
vd = VirtualDatabaseTableProvider.getInstance(getApplicationContext());
fm = getSupportFragmentManager();
fm.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
//Create Layout and add fragments
setContentView(R.layout.main_window);
ListFragment ListFragment= new ListFragment();
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.replace(R.id.fragment_pane, ListFragment, "List");
//ft.replace(R.id.fragment_pane, ListFragment);
ft.addToBackStack(null);
ft.commit();
//Initialising buttons
imgBtnFontInc = (ImageButton) findViewById(R.id.ImgBtnUpFont);
imgBtnFontInc.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
if(textViewAttached){
try{
//Some text Resize
}
}catch (NullPointerException npe){
Log.e(TAG, "Error on calling Text resize");
Log.e(TAG, npe.getMessage());
Log.e(TAG, npe.getStackTrace().toString());
}
}
}
}
);
/* More Buttons code..... */
imgBtnFontDec.setVisibility(View.GONE);
imgBtnFontInc.setVisibility(View.GONE);
/* Some Saved State handling to recover detailed text Screen*/
if(savedInstanceState != null){
if (savedInstanceState.containsKey("UUID")){
try{
String uuid = savedInstanceState.getString("UUID");
if (uuid != null){
iniTextScreen(uuid);
}
}catch (Exception e){
Log.e(TAG, "Unable To return text");
}
}
}
Text is initialised with function:
private void initTextScreen(String StringID){
Bundle Data = new Bundle();
Data.putString("UUID", StringID);
TextScreenFragment TextFragment = new TextScreenFragment();
TextFragment.setArg1ments(Data);
if(fm == null){
fm = getSupportFragmentManager();
}
android.support.v4.app.FragmentTransaction ft = fm.beginTransaction();
ft.setCustomAnimations( R.anim.animation_enter, R.anim.animation_exit);
ft.replace(R.id.fragment_pane, TextFragment, "TextFragment");
ft.addToBackStack(null);
ft.commit();
}
I handled Buttons visibility in main activity with simple Callback from TextScreenFragment. Callback in main activity:
public void onTextViewAttached() {
textViewAttached = true;
MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);
}
Callback called in TextScreenFragment:
#Override
public void onAttach(Activity activity) {
super.onAttach(activity);
if (!(activity instanceof Callbacks)) {
throw new IllegalStateException(
"Activity must implement fragment's callbacks.");
} else {
listener = (Callbacks) activity;
listener.onTextViewAttached();
}
}
public interface Callbacks {
/**
* Callback for when an item has been selected.
*/
public void onTextViewAttached();
}
It works, however when I put android phone is switch potrait/landscape mode: onAttached in fragment get called way before onCreate in main Activity and Button objects.
How can fragment be attached on main activity before even onCreate is called in main activity?
I attached a particular fragment at very end of onCreate method after buttons were already initialized,but why onAttach in fragment is called before even I attach fragment and get null exception, because button objects were not initialized in onCreate? How it is even possible?
When I comment out:
`// MainActivity.this.imgBtnFontDec.setVisibility(View.VISIBLE);
//MainActivity.this.imgBtnFontInc.setVisibility(View.VISIBLE);`
in callback function public void onTextViewAttached(), no more crashes, but still I noticed that onAttach is called before main activity created and is called twice:
one time with uninitialised activity from hell knows were (every element of main activity is either null or has default values), second time when fragment is properly attached from main activity onCreate.
I made conclusion that on orientation switch fragments gets atached to uninitialised activity. Am I missing something, on orientation change should I call some other function before onCreate in main activity to get buttons from layout?
Is it some kind of fragment automated attach behaviour which I am not aware of and I could take advantage of?
What is life cycle of activity with fragment attached to it, because onAttach called in fragment before even main activity is created seems counter intuitive.

How to properly remove retained instance Fragment

Currently, I would like to retain an expensive data structure, during configuration changes. I choose not to use Bundle to handle it, as the expensive data structure is not parcelable.
Hence, I use a non-UI Fragment (Called it RetainInstanceFragment), with its setRetainInstance(true) to hold the data structure.
public class RetainInstanceFragment extends Fragment {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Creating expensive data structure
expensiveDataStructure = CreateExpensiveDataStructure();
// Tell the framework to try to keep this fragment around
// during a configuration change.
setRetainInstance(true);
}
public ExpensiveDataStructure expensiveDataStructure = null;
}
An UI Fragment (Called it UIFragment) will get the expensive data structure from RetainInstanceFragment. Whenever there is configuration changes on UIFragment, UIFragment will always try to get the "cached" RetainInstanceFragment from FragmentManager, before it decides to create a new RetainInstanceFragment.
Example code is as follow.
public class UIFragment extends SherlockListFragment
#Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Check to see if we have retained the worker fragment.
retainInstanceFragment = (RetainInstanceFragment)fm.findFragmentByTag("data");
// If not retained (or first time running), we need to create it.
if (retainInstanceFragment == null) {
retainInstanceFragment = new RetainInstanceFragment();
fm.beginTransaction().add(watchlistArrayFragment, "data").commit();
} else {
// We can re-use retainInstanceFragment.expensiveDataStructure even
// after configuration change.
}
}
}
However, there's a problem. Whenever I destroy my old UIFragment, and replace it with new UIFragment, I expect old RetainInstanceFragment will be destroyed as well. Here is how I destroy and create new UIFragment
public class MyFragmentActivity extends SlidingFragmentActivity
// Being triggered when there is different menu item in sliding menu being
// selected.
public void selectActiveContent(Country country) {
Fragment fragment = new UIFragment(country);
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commitAllowingStateLoss();
}
But old RetainInstanceFragment is never destroyed.
My guess is, perhaps I forget to perform clean up in UIFragment. Hence, I add the following code
UIFragment
#Override
public void onDetach() {
super.onDetach();
// To differentiate whether this is a configuration changes, or we are
// removing away this fragment?
if (this.isRemoving()) {
FragmentManager fm = getFragmentManager();
fm.beginTransaction().remove(retainInstanceFragment).commit();
}
}
However, it doesn't work all the time. I perform several sliding menu clicks.
1. selectActiveContent() -> Create new UIFragment and new RetainInstanceFragment
2. selectActiveContent() -> Create new UIFragment, but re-use previous RetainInstanceFragment. (Wrong behavior)
3. selectActiveContent() -> Create new UIFragment, and new RetainInstanceFragment.
4. selectActiveContent() -> Create new UIFragment, but re-use previous RetainInstanceFragment. (Wrong behavior)
Any idea how I can properly remove retained instance Fragment?
As suggested by #Luksprog, the following method works. However, it still do not explain why the previous cleanup done through onDetach doesn't work. If anyone can explain why this solution works and previous doesn't, I would be very thankful. :)
UIFragment
#Override
public void onDetach() {
super.onDetach();
}
public void cleanupRetainInstanceFragment() {
FragmentManager fm = getFragmentManager();
fm.beginTransaction().remove(this.retainInstanceFragment).commit();
}
MyFragmentActivity
public class MyFragmentActivity extends SlidingFragmentActivity
// Being triggered when there is different menu item in sliding menu being
// selected.
public void selectActiveContent(Country country) {
// *******************************************
// Solution suggested by #Luksprog. It works!
// But I have no idea why it works and previous doesn't work...
// *******************************************
Fragment oldFragment = getSupportFragmentManager().findFragmentById(R.id.content);
if (oldFragment instanceof UIFragment) {
((UIFragment)oldFragment).cleanupRetainInstanceFragment();
}
Fragment fragment = new UIFragment(country);
getSupportFragmentManager().beginTransaction().replace(R.id.content, fragment).commitAllowingStateLoss();
}
(Edited) Useful comment by #Luksprog
The fragment transactions are not made right away. My assumption was
that doing that transaction in the onDetach() callback will not remove
the retain fragment instance before the UI fragment's replace
transaction finished and so your new UI fragment will still see the
retain fragment instance still available, so it will not create a new
one. Your previous method is not in the spirit of the fragments
framework where fragments are unaware of other fragments and the
activity manages all of them as it knows more about the overall
application state.
I think you can just remove the fragment from fragment transaction.
if (mWorkFragment != null) {
fm.beginTransaction().remove(mWorkFragment).commitAllowingStateLoss();
}

Categories

Resources