Fragments (Java)

A Fragment is a reusable portion of UI + behavior that you place inside an Activity (or another Fragment’s view). Fragments have their own lifecycle, participate in the Activity’s back stack, and make multi-pane/tablet UIs easier. Think of them as screens inside a screen.

When to Use Fragments

Reusable UI

Same card/list used in multiple Activities

Adaptive Layouts

Phone (single pane) vs Tablet (two-pane)

Dialog UI

Use DialogFragment

Nav Host

Navigation Component destination

onAttachonCreateonCreateViewonViewCreatedonStartonResumeonPauseonStoponDestroyViewonDestroyonDetach View created View destroyed (release bindings!)
Key idea: A Fragment’s view can be destroyed (onDestroyView()) while the Fragment still exists. Release view bindings here to avoid memory leaks.

Activity vs Fragment (At a Glance)

AspectActivityFragment
PurposeTop-level screenReusable UI block inside Activity
Lifecycle OwnerYesYes (getViewLifecycleOwner() for Views)
NavigationstartActivity / NavHostFragmentTransaction / NavController
Back StackSystem back stackFragmentManager back stack
UI ContainerWindow DecorViewFragmentContainerView / any container

Create a Fragment (Java)

// HelloFragment.java
public class HelloFragment extends Fragment {

  public static HelloFragment newInstance(String name) {
    HelloFragment f = new HelloFragment();
    Bundle args = new Bundle();
    args.putString("name", name);
    f.setArguments(args);
    return f;
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    return inflater.inflate(R.layout.fragment_hello, container, false);
  }

  @Override
  public void onViewCreated(@NonNull View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    String name = getArguments() != null ? getArguments().getString("name") : "Guest";
    TextView tv = view.findViewById(R.id.tvHello);
    tv.setText("Hello, " + name);
  }

  @Override
  public void onDestroyView() {
    super.onDestroyView();
    // null out view references or viewBinding objects here
  }
}

Layout

<!-- res/layout/fragment_hello.xml -->
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">

  <TextView
      android:id="@+id/tvHello"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="Hello Fragment"
      android:textSize="22sp" />
</FrameLayout>

Adding Fragments to an Activity

1) In XML with FragmentContainerView

<!-- res/layout/activity_host.xml -->
<androidx.fragment.app.FragmentContainerView
    android:id="@+id/fragment_container"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

2) Dynamically via FragmentTransaction

// In HostActivity.java
Fragment f = HelloFragment.newInstance("Sonu");
getSupportFragmentManager()
  .beginTransaction()
  .replace(R.id.fragment_container, f)  // or add()
  .addToBackStack(null)               // optional: back navigates previous fragment
  .commit();
Tip: Use replace() for simple swaps; use add()/hide()/show() when you want to keep instances alive.

Back Stack & Transactions

APIEffectNotes
addToBackStack(name)Push transaction on stackBack button will reverse it
commit()AsynchronousSafe for UI-thread usage
commitNow()SynchronousDo only when needed; must be on main thread
commitAllowingStateLoss()May lose stateLast resort (e.g., during onStop); avoid if possible

Communication Patterns

1) Activity ↔ Fragment using interface (classic)

// In Fragment
public interface OnHelloClick {
  void onHello(String name);
}
private OnHelloClick callback;

@Override public void onAttach(@NonNull Context ctx) {
  super.onAttach(ctx);
  if (ctx instanceof OnHelloClick) callback = (OnHelloClick) ctx;
}

private void notifyHello(){ if(callback!=null) callback.onHello("Sonu"); }

2) Shared ViewModel (recommended for siblings)

// Shared ViewModel scoped to Activity
public class SharedVM extends ViewModel {
  public MutableLiveData<String> selected = new MutableLiveData<>();
}

// In both fragments
SharedVM vm = new ViewModelProvider(requireActivity()).get(SharedVM.class);
vm.selected.observe(getViewLifecycleOwner(), value -> { // update UI  });

DialogFragment

public class ConfirmDialog extends DialogFragment {
  @NonNull @Override
  public Dialog onCreateDialog(Bundle s) {
    return new AlertDialog.Builder(getActivity())
      .setTitle("Confirm")
      .setMessage("Proceed?")
      .setPositiveButton("Yes", (d,w) -> {/* ... */})
      .setNegativeButton("No", null)
      .create();
  }
}

Child Fragments

Fragments can host other fragments (tabs, pagers). Use getChildFragmentManager() for nested transactions.

RecyclerView inside Fragment

@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle s) {
  return inflater.inflate(R.layout.fragment_list, container, false);
}
@Override
public void onViewCreated(@NonNull View v, Bundle s) {
  RecyclerView rv = v.findViewById(R.id.recycler);
  rv.setLayoutManager(new LinearLayoutManager(getContext()));
  rv.setAdapter(new ItemsAdapter());
}

State & Restoration

  • Use setArguments(Bundle) for construction params (persisted automatically).
  • Put UI state in ViewModel; release view references in onDestroyView().
  • Let the system recreate fragments; avoid manual fragment caching in singletons.

Where to Keep Data

ViewModel (screen state)
Arguments (construction params)
savedInstanceState (small UI)

Common Pitfalls

Leaking views after onDestroyView
StateLoss commits
Overusing replace()

Fragment Results (no direct interface)

// Sender (child fragment)
Bundle result = new Bundle();
result.putString("selected", "Item-42");
getParentFragmentManager().setFragmentResult("pick_key", result);

// Receiver (parent or host fragment/activity)
getSupportFragmentManager().setFragmentResultListener("pick_key", this, (req, bundle) -> {
  String id = bundle.getString("selected");
  // use id
});

FragmentFactory (advanced)

Inject dependencies into fragments via a custom FragmentFactory so the system uses your constructor.

public class MyFactory extends FragmentFactory {
  private final Repo repo;
  public MyFactory(Repo r){ repo = r; }
  @NonNull @Override
  public Fragment instantiate(@NonNull ClassLoader cl, @NonNull String className) {
    if(className.equals(HelloFragment.class.getName())) {
      return new HelloFragment(repo);
    }
    return super.instantiate(cl, className);
  }
}

Troubleshooting

“Can’t perform this action after onSaveInstanceState” ➜ You’re committing after state saved. Move transaction earlier or avoid using commitAllowingStateLoss() unless absolutely necessary.

Leaking Views ➜ Null out view bindings in onDestroyView(), not in onDestroy().

Back button not working ➜ Did you call addToBackStack()? For Nav Component, use NavController.

Nested fragments not showing ➜ Use getChildFragmentManager() instead of host manager.

Best Practices

  • Use ViewBinding or findViewById and clear references in onDestroyView().
  • Keep Fragments UI-focused; put business logic in ViewModels/repositories.
  • Prefer FragmentContainerView over the old <fragment> tag.
  • Share data across fragments with a shared ViewModel scoped to the Activity.
  • For complex flows, use the Navigation Component (it manages back stack & arguments safely).

Hands-On Exercise

  1. Create ListFragment (shows items) and DetailFragment (shows details).
  2. On item click, navigate to DetailFragment with the selected ID in arguments.
  3. Use a shared ViewModel to update a toolbar title based on the selected item.
  4. Add transactions to the back stack and verify back navigation works.
Single-activity app └─ HostActivity (FragmentContainerView) ├─ ListFragment (select item) │ └─ set result / update shared VM └─ DetailFragment (observes VM) → renders details