Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions build/fragments.html
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ <h1><span class="header-section-number">Lecture 6</span> Fragments</h1>
<h2><span class="header-section-number">6.1</span> Creating a Fragment</h2>
<p>In order to illustrate how to make a Fragment, we will <a href="https://en.wikipedia.org/wiki/Code_refactoring"><strong>refactor</strong></a> the <code>MainActivity</code> to use Fragments for displaying the list of movies. This will help to illustrate the relationship between Activities and Fragments.</p>
<p>To create a Fragment, you subclass the <code>Fragment</code> class. Let’s make one called <code>MovieListFragment</code> (in a <code>MovieListFragment.java</code> file). You can use Android Studio to do this work: via the <code>File &gt; New &gt; Fragment &gt; Fragment (blank)</code> menu option. (<strong>DO NOT</strong> select any of the other options for in the wizard for now; they provide template code that can distract from the core principles).</p>
<p>There are two versions of the <code>Fragment</code> class: one in the framework’s <code>android.app</code> package and one in the <code>android.support.v4</code> package. The later package refers to the <a href="https://developer.android.com/topic/libraries/support-library/index.html">Support Library</a>. As discussed in the previous chapter, these are libraries of classes designed to make Android applications <em>backwards compatible</em>: for example, <code>Fragment</code> and its related classes came out in API 11 so aren’t in the <code>android.app</code> package for earlier devices. By including the support library, we can include those classes on older devices as well!</p>
<p>There are two versions of the <code>Fragment</code> class: one in the framework’s <code>android.app</code> package and one in the <code>android.support.v4</code> package. The latter package refers to the <a href="https://developer.android.com/topic/libraries/support-library/index.html">Support Library</a>. As discussed in the previous chapter, these are libraries of classes designed to make Android applications <em>backwards compatible</em>: for example, <code>Fragment</code> and its related classes came out in API 11 so aren’t in the <code>android.app</code> package for earlier devices. By including the support library, we can include those classes on older devices as well!</p>
<ul>
<li><p>Support libraries <em>also</em> include additional convenience and helper classes that are not part of the core Android package. These include interface elements (e.g., <code>ConstraintLayout</code>, <code>RecyclerView</code>, or <a href="https://developer.android.com/reference/android/support/v4/view/ViewPager.html"><code>ViewPager</code></a>) and <a href="https://developer.android.com/reference/android/support/v4/view/accessibility/package-summary.html">accessibility</a> classes. See <a href="https://developer.android.com/topic/libraries/support-library/features.html">the features list</a> for details. Thus it is often useful to include and utilize support library versions of classes even when targeting later devies so that you don’t need to “roll your own” versions of these convenience classes.</p></li>
<li><p>The main disadvantage to using support libraries is that they need to be included in your application, so will make the final <code>.apk</code> file larger (and may potentially require workarounds for method count limitations). You will also run into problems if you try and mix and match versions of the classes (e.g., from different versions of the support library). But as always, you should <em>avoid premature optimization</em>. Thus in this course you should <strong>default</strong> to using the support library version of a class when given a choice!</p></li>
Expand Down Expand Up @@ -372,7 +372,7 @@ <h3>Activity-to-Fragment Communication</h3>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="co">//MovieListFragment example</span>
MovieListFragment fragment = (MovieListFragment)<span class="fu">getSupportFragmentManager</span>().<span class="fu">findFragmentById</span>(R.<span class="fu">id</span>.<span class="fu">fragment</span>);</code></pre></div>
<ul>
<li>Note that we’re using a method to explicit access the <strong>support</strong> <code>FragmentManager</code>. The Activity class (API level 15+) is able to work with both the platform and support <code>FragmentManager</code> classes. But because these classes don’t have a shared <code>interface</code>, the Activity needs to provide different Java methods which can return the correct type.</li>
<li>Note that we’re using a method to explicitly access the <strong>support</strong> <code>FragmentManager</code>. The Activity class (API level 15+) is able to work with both the platform and support <code>FragmentManager</code> classes. But because these classes don’t have a shared <code>interface</code>, the Activity needs to provide different Java methods which can return the correct type.</li>
</ul>
<p>Once you have a reference to the Fragment, this acts just like any other object—you can call any <code>public</code> methods it has! For example, if you give the Fragment a public method (e.g., <code>searchMovies()</code>), then this method can be called from the Activity:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="co">//called from Activity on the referenced fragment</span>
Expand All @@ -393,8 +393,8 @@ <h2><span class="header-section-number">6.2</span> Dynamic Fragments</h2>
<p>In this section, we will continue to refine the Movie app so that when the user clicks on a Movie in the list, the app shows a screen (Fragment) with details about the selected movie.</p>
<div id="instantiating-fragments" class="section level3 unnumbered">
<h3>Instantiating Fragments</h3>
<p>To do this, we will need to instantiate the desired Fragment <strong>dynamically</strong> (in Java code), rather than statically in the XML using the <code>&lt;fragment&gt;</code> element. This is because we need to be able to dynamically change <em>which</em> Fragment is currently being shown, which is not possibly for Fragments that are “hard-coded” in the XML.</p>
<p>Unlike Activities, Fragments (such as <code>MovieListFragment</code>) <strong>do</strong> have constructor methods that can be called. In fact, Android <em>requires</em> that every Fragment include a default (no-argument) constructor that is called when Fragments are created by the system! While we are have access to the constructor, it is considered best practice to <strong>not</strong> call this constructor directly when you want to instantiate a Fragment, and to in fact leave the method empty. This is because we do not have full control over when the constructor is executed: the Android system may call the no-argument constructor whenever it needs to recreate the Activity (or just the Fragment), which can happen at arbitrary times. Since only this default constructor is called, we can’t add an additional constructor with any arguments we may want the Fragment to have (e.g., the <code>searchTerm</code>)… and thus it’s best to not use it at all.</p>
<p>To do this, we will need to instantiate the desired Fragment <strong>dynamically</strong> (in Java code), rather than statically in the XML using the <code>&lt;fragment&gt;</code> element. This is because we need to be able to dynamically change <em>which</em> Fragment is currently being shown, which is not possible for Fragments that are “hard-coded” in the XML.</p>
<p>Unlike Activities, Fragments (such as <code>MovieListFragment</code>) <strong>do</strong> have constructor methods that can be called. In fact, Android <em>requires</em> that every Fragment include a default (no-argument) constructor that is called when Fragments are created by the system! While we have access to the constructor, it is considered best practice to <strong>not</strong> call this constructor directly when you want to instantiate a Fragment, and to in fact leave the method empty. This is because we do not have full control over when the constructor is executed: the Android system may call the no-argument constructor whenever it needs to recreate the Activity (or just the Fragment), which can happen at arbitrary times. Since only this default constructor is called, we can’t add an additional constructor with any arguments we may want the Fragment to have (e.g., the <code>searchTerm</code>)… and thus it’s best to not use it at all.</p>
<p>Instead, we specify a <strong>simple factory</strong> method (by convention called <code>newInstance()</code>) which is able to “create” an instance of the Fragment for us. This factory method can take as many arguments as we want, and then does the work of passing these arguments into the Fragment instantiated with the default constructor:</p>
<div class="sourceCode"><pre class="sourceCode java"><code class="sourceCode java"><span class="kw">public</span> <span class="dt">static</span> MyFragment <span class="fu">newInstance</span>(String argument) {
MyFragment fragment = <span class="kw">new</span> <span class="fu">MyFragment</span>(); <span class="co">//instantiate the Fragment</span>
Expand All @@ -403,7 +403,7 @@ <h3>Instantiating Fragments</h3>
fragment.<span class="fu">setArguments</span>(args); <span class="co">//add the Bundle to the Fragment</span>
<span class="kw">return</span> fragment; <span class="co">//return the Fragment</span>
}</code></pre></div>
<p>In order to pass the arguments into the new Fragment, we wrap them up in a <a href="https://developer.android.com/reference/android/os/Bundle.html"><code>Bundle</code></a> (an object containing basic <em>key-value pairs</em>). Values can be added to a <code>Bundle</code> using an appropriate <code>putType()</code> method; note that these do need to be primitive types (<code>int</code>, <code>String</code>, etc.). The <code>Bundle</code> of arguments can then be assignment to the Fragment by calling the <a href="http://developer.android.com/reference/android/app/Fragment.html#setArguments(android.os.Bundle)"><code>setArguments()</code></a> method.</p>
<p>In order to pass the arguments into the new Fragment, we wrap them up in a <a href="https://developer.android.com/reference/android/os/Bundle.html"><code>Bundle</code></a> (an object containing basic <em>key-value pairs</em>). Values can be added to a <code>Bundle</code> using an appropriate <code>putType()</code> method; note that these do need to be primitive types (e.g. <code>int</code>), <code>String</code> objects, or <code>Parcelable</code> objects. The <code>Bundle</code> of arguments can then be assignment to the Fragment by calling the <a href="http://developer.android.com/reference/android/app/Fragment.html#setArguments(android.os.Bundle)"><code>setArguments()</code></a> method.</p>
<ul>
<li><p>We will be able to access this <code>Bundle</code> from inside the Fragment (e.g., in the <code>onCreateView()</code> callback) by using the <code>getArguments()</code> method (and <code>getType()</code> to retrieve the values from it). This allows us to dynamically adjust the content of the Fragment’s Views! For example, we can run the <code>downloadMovieData()</code> function using this argument, fetching movie results as soon as the Fragment is created (e.g., on a button press)… allowing the <code>downloadMovieData()</code> function to again be made private, for example.</p></li>
<li><p>Since the <code>Bundle</code> is a set of <em>key-value</em> pairs, each value needs to have a particular key. These keys are usually defined as <code>private</code> constants (e.g., <code>ARG_PARAM_KEY</code> in the above example) to make storage and retrieval easier.</p></li>
Expand Down Expand Up @@ -433,7 +433,7 @@ <h3>Transactions</h3>
</div>
<div id="inter-fragment-communication" class="section level3 unnumbered">
<h3>Inter-Fragment Communication</h3>
<p>We can this transaction-based structure to instantiate and load a <strong>second Fragment</strong> (e.g., a “detail” view for a selected Movie). We can add functionality (e.g., in the <code>onClick()</code> handler) so that when the user clicks on a movie in the list, we <strong><code>replace()</code></strong> the currently displayed Fragment with this new details Fragment.</p>
<p>We can use this transaction-based structure to instantiate and load a <strong>second Fragment</strong> (e.g., a “detail” view for a selected Movie). We can add functionality (e.g., in the <code>onClick()</code> handler) so that when the user clicks on a movie in the list, we <strong><code>replace()</code></strong> the currently displayed Fragment with this new details Fragment.</p>
<p>However, remember that Fragments are supposed to be <strong>modular</strong>—each Fragment should be <em>self-contained</em>, and not know about any other Fragments that may exist (after all, what if we wanted the master/detail views to be side-by-side on a large screen?)</p>
<p class="alert alert-warning">
Using <code>getActivity()</code> to reference the Activity and <code>getSupportFragmentManager()</code> to access the manager is a violation of the <a href="http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf">Law of Demeter</a>—don’t do it!
Expand Down Expand Up @@ -463,7 +463,8 @@ <h3>Inter-Fragment Communication</h3>
</p>
<div id="parcelable" class="section level4 unnumbered">
<h4>Parcelable</h4>
<p>It’s not uncommon to want to pass multiple or complex data values as arguments to a new Fragment, such as a <code>Movie</code> object. However, the <code>arguments</code> for a new Fragment must be passed in through a <code>Bundle</code>, which are only able to store primitive values (e.g., <code>int</code>, <code>float</code>, <code>String</code>)… and a special data type called <a href="https://developer.android.com/reference/android/os/Parcelable.html"><strong>Parcelable</strong></a>. An object that implements the <code>Parcelable</code> interface includes methods that allow it to be <em>serialized</em> into a value that is simple enough to place in a Bundle—in effect, it’s values are converted into a formatted String (similar in concept to <code>JSON.stringify()</code> in JavaScript).</p>
<p>It’s not uncommon to want to pass multiple or complex data values as arguments to a new Fragment, such as a <code>Movie</code> object. However, the <code>arguments</code> for a new Fragment must be passed in through a <code>Bundle</code>, which are only able to store primitive values (e.g., <code>int</code>, <code>float</code>), <code>String</code> objects, and a special data type called <a href="https://developer.android.com/reference/android/os/Parcelable.html"><strong>Parcelable</strong></a>. An object that implements the <code>Parcelable</code> interface includes methods that allow it to be <em>serialized</em> into a value that is simple enough to place in a Bundle—in effect, it’s values are converted into a formatted String (similar in concept to <code>JSON.stringify()</code> in JavaScript).</p>
<!-- Strings are technically not primitive types. See https://docs.oracle.com/javase/tutorial/java/nutsandbolts/datatypes.html. -->
<p>Implementing the <code>Parcelable</code> interface involves the following steps: 1. provide a <code>writeToParcel()</code> method that adds the object’s fields to a given <code>Parcel</code> object 2. provide a constructor that can re-create the object from a <code>Parcel</code> by reading the values from it (<em>in the EXACT SAME order they were added</em>!) 3. provide a <code>describeContents()</code> that returns whether the parcel includes a file descriptor (usually <code>0</code>, meaning not) 4. provide a <code>Parcelable.Creator</code> <em>constant</em> that can be used to create the Parcelable.</p>
<p>These steps seem complex but are fairly rote: as such, it’s possible to “automate” making a class into a Parcelable. My favorite strategy is to use <strong><a href="http://www.parcelabler.com/" class="uri">http://www.parcelabler.com/</a></strong>, a website that allows you to copy and paste a class and provides you a version of that class with the Parcelable interface implemented!</p>
<p class="alert alert-danger">
Expand Down