Activity
stack, but you stay within one Activity
).Context
Android context is a divine object , and the Activity
is a Context
with an additional life cycle. Life cycle deity? Ironically. Fragments of the divine pantheon are not included, but they are more than compensate for this deficiency in a very complex life cycle.FragmentManagerImpl
class. Careful, Mina! switch (f.mState) { case Fragment.INITIALIZING: if (f.mSavedFragmentState != null) { f.mSavedViewState = f.mSavedFragmentState.getSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG); f.mTarget = getFragment(f.mSavedFragmentState, FragmentManagerImpl.TARGET_STATE_TAG); if (f.mTarget != null) { f.mTargetRequestCode = f.mSavedFragmentState.getInt( FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); } f.mUserVisibleHint = f.mSavedFragmentState.getBoolean( FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true); if (!f.mUserVisibleHint) { f.mDeferStart = true; if (newState > Fragment.STOPPED) { newState = Fragment.STOPPED; } } } // ... }
Activity
fragment, then you understand what I am saying (and for the sake of all that is holy, do not tempt fate and don’t mention about nested fragments with me).FragmentManager
class. Take a look at the following code, everything is simple and clear, right? DialogFragment dialogFragment = new DialogFragment() { @Override public Dialog onCreateDialog(Bundle savedInstanceState) { ... } }; dialogFragment.show(fragmentManager, tag);
Activity
state is restored, the FragmentManager
may attempt to re-create the fragment through reflection. Since we created an anonymous class at the top, there is a hidden argument in its constructor that refers to the external class. Bams: android.support.v4.app.Fragment$InstantiationException: Unable to instantiate fragment com.squareup.MyActivity$1: make sure class name exists, is public, and has an empty constructor that is public
Activity
for each screen. We can spread our interface across individual widgets and arrange them as we need. This simplifies the animation of the interface and the life cycle. We can also divide our widgets into presentation classes and controller classes.Activity
; You can safely create it inside a single Activity
.Activity
, Views
, Layout Inflaters
) was in Android from the very beginning.HeadlinesFragment
is a list of elements: public class HeadlinesFragment extends ListFragment { OnHeadlineSelectedListener mCallback; public interface OnHeadlineSelectedListener { void onArticleSelected(int position); } @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setListAdapter( new ArrayAdapter<String>(getActivity(), R.layout.fragment_list, Ipsum.Headlines)); } @Override public void onAttach(Activity activity) { super.onAttach(activity); mCallback = (OnHeadlineSelectedListener) activity; } @Override public void onListItemClick(ListView l, View v, int position, long id) { mCallback.onArticleSelected(position); getListView().setItemChecked(position, true); } }
ListFragmentActivity
more interesting: ListFragmentActivity
should deal with whether to show the details on the same screen as the list or not: public class ListFragmentActivity extends Activity implements HeadlinesFragment.OnHeadlineSelectedListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.news_articles); if (findViewById(R.id.fragment_container) != null) { if (savedInstanceState != null) { return; } HeadlinesFragment firstFragment = new HeadlinesFragment(); firstFragment.setArguments(getIntent().getExtras()); getFragmentManager() .beginTransaction() .add(R.id.fragment_container, firstFragment) .commit(); } } public void onArticleSelected(int position) { ArticleFragment articleFrag = (ArticleFragment) getFragmentManager() .findFragmentById(R.id.article_fragment); if (articleFrag != null) { articleFrag.updateArticleView(position); } else { ArticleFragment newFragment = new ArticleFragment(); Bundle args = new Bundle(); args.putInt(ArticleFragment.ARG_POSITION, position); newFragment.setArguments(args); getFragmentManager() .beginTransaction() .replace(R.id.fragment_container, newFragment) .addToBackStack(null) .commit(); } } }
Container
), which can show an element of the list, and also handles clicks back: public interface Container { void showItem(String item); boolean onBackPressed(); }
Activity
knows that it always has a container in its hands, and simply delegates the necessary work to it: public class MainActivity extends Activity { private Container container; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main_activity); container = (Container) findViewById(R.id.container); } public Container getContainer() { return container; } @Override public void onBackPressed() { boolean handled = container.onBackPressed(); if (!handled) { finish(); } } }
public class ItemListView extends ListView { public ItemListView(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); final MyListAdapter adapter = new MyListAdapter(); setAdapter(adapter); setOnItemClickListener(new OnItemClickListener() { @Override public void onItemClick(AdapterView<?> parent, View view, int position, long id) { String item = adapter.getItem(position); MainActivity activity = (MainActivity) getContext(); Container container = activity.getContainer(); container.showItem(item); } }); } }
<com.squareup.view.SinglePaneContainer xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:id="@+id/container" > <com.squareup.view.ItemListView android:layout_width="match_parent" android:layout_height="match_parent" /> </com.squareup.view.SinglePaneContainer>
<com.squareup.view.DualPaneContainer xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="horizontal" android:id="@+id/container" > <com.squareup.view.ItemListView android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="0.2" /> <include layout="@layout/detail" android:layout_width="0dp" android:layout_height="match_parent" android:layout_weight="0.8" /> </com.squareup.view.DualPaneContainer>
public class DualPaneContainer extends LinearLayout implements Container { private MyDetailView detailView; public DualPaneContainer(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); detailView = (MyDetailView) getChildAt(1); } public boolean onBackPressed() { return false; } @Override public void showItem(String item) { detailView.setItem(item); } }
public class SinglePaneContainer extends FrameLayout implements Container { private ItemListView listView; public SinglePaneContainer(Context context, AttributeSet attrs) { super(context, attrs); } @Override protected void onFinishInflate() { super.onFinishInflate(); listView = (ItemListView) getChildAt(0); } public boolean onBackPressed() { if (!listViewAttached()) { removeViewAt(0); addView(listView); return true; } return false; } @Override public void showItem(String item) { if (listViewAttached()) { removeViewAt(0); View.inflate(getContext(), R.layout.detail, this); } MyDetailView detailView = (MyDetailView) getChildAt(0); detailView.setItem(item); } private boolean listViewAttached() { return listView.getParent() != null; } }
Presenters
). Introduction of presenters will allow us to make the code more readable and simplify further testing: public class MyDetailView extends LinearLayout { TextView textView; DetailPresenter presenter; public MyDetailView(Context context, AttributeSet attrs) { super(context, attrs); presenter = new DetailPresenter(); } @Override protected void onFinishInflate() { super.onFinishInflate(); presenter.setView(this); textView = (TextView) findViewById(R.id.text); findViewById(R.id.button).setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { presenter.buttonClicked(); } }); } public void setItem(String item) { textView.setText(item); } }
class EditDiscountPresenter { // ... public void saveDiscount() { EditDiscountView view = getView(); String name = view.getName(); if (isBlank(name)) { view.showNameRequiredWarning(); return; } if (isNewDiscount()) { createNewDiscountAsync(name, view.getAmount(), view.isPercentage()); } else { updateNewDiscountAsync(discountId, name, view.getAmount(), view.isPercentage()); } close(); } }
@Test public void cannot_save_discount_with_empty_name() { startEditingLoadedPercentageDiscount(); when(view.getName()).thenReturn(""); presenter.saveDiscount(); verify(view).showNameRequiredWarning(); assertThat(isSavingInBackground()).isFalse(); }
public class DetailFragment extends Fragment { @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { return inflater.inflate(R.layout.my_detail_view, container, false); } }
Bundle
to survive the death process.Activity
life cycle. Thus, you can create scopes: as soon as the presentation appears on the screen, Dagger / Mortar creates its presenter and other dependencies. When the view leaves the screen, you destroy this scope (containing the presenter and the dependencies) and the garbage collector takes over.Source: https://habr.com/ru/post/277289/
All Articles