Design Patterns, Fragments, and The Real

Download as pptx, pdf, or txt
Download as pptx, pdf, or txt
You are on page 1of 53

Design Patterns, Fragments, and

the Real World


• You will learn about the following topics in this
chapter:
• Patterns and the model-view-controller
pattern
• Android design guidelines
• Getting started with real-world designs and
handling multiple different devices
• An introduction to Fragments
Introducing the model-view-controller pattern

• MVC refers to the separation of different


aspects of our app into distinct parts called
layers.
• Android apps commonly use the model-view-
controller pattern.
• A pattern is simply a recognized way to
structure our code and other application
resources, such as layout files, images,
databases, and so on.
The model
• The model refers to the data that drives our
app and any logic/code that specifically
manages it and makes it available to the other
layers. For example, in our Note To Self app,
the Note class along with its getters, setters,
and JSON code was the data and logic.
The view
• The view of the Note To Self app referred to all
the widgets in all the different layouts.
Anything the user can see or interact with on
screen is typically part of the view. And as you
can probably remember, the widgets actually
came from the View class hierarchy of the
Android API.
The controller
• The controller is the bit in between the view and
model. It interacts with both and also keeps them
separate. It contains what is known in geek language
as the application logic. If a user clicks on a button,
the application layer decides what to do about it.
• When the user clicks on OK to add a new note, the
application logic listens for the interaction on the
view layer. It captures the data contained in the view
and passes it to the model layer.
Android design guidelines
• So, what exactly do I mean by design? I am
talking about where you put the widgets on
the screen, which widgets, what color should
they be, how big should they be, how to
transition between screens, the best way to
scroll a page, when and which interpolators to
use, what screens should your app be divided
into, and much more.
The device detection mini app
• To make this app, create a new project and call
it Device Detection. Delete the default Hello
world! widget. Drag Button onto the top of
the screen and set its onClick property to
detectDevice
• Drag two LargeText widgets onto the layout
and set their id properties to txtOrientation
and txtResolution, respectively. You should
now have a layout that looks something like
this:
• Add the following members just after the
MainActivity class declaration to hold
references to our two TextView widgets:
• private TextView txtOrientation;
• private TextView txtResolution;
• Now, in the onCreate method of MainActivity,
just after the call to setContentView, add this
code:
• // Get a reference to our TextView widgets
• txtOrientation = (TextView)
findViewById(R.id.txtOrientation);
• txtResolution = (TextView)
findViewById(R.id.txtResolution);
• After onCreate, add the method that handles our button click and runs our detection code:
• public void detectDevice(View v){
• // What is the orientation?
• Display display = getWindowManager().getDefaultDisplay();
• txtOrientation.setText("" + display.getRotation());
• // What is the resolution?
• Point xy = new Point();
• display.getSize(xy);
• txtResolution.setText("x = " + xy.x + " y = " + xy.y);
• }
• Rotate the device to landscape (use Ctrl + F11 on PC or Ctrl + fn + F11 on Mac). Now, click
on the NEW BUTTON button .
• If the 0 and 1 results are less than obvious regarding the device orientation, they refer to
the public static final variables of the Surface class, where Surfcae. ROTATION_0 equals 0
and Surface.ROTATION_180 equals 1.
Configuration qualifiers
• We have already seen configuration qualifiers such as
layout-large or layout-xhdpi.
• There are configuration qualifiers for size,
orientation, and pixel density.
• To take advantage of a configuration qualifier, we
simply design a layout in the usual way that is
optimized for our preferred configuration and then
place that layout in a folder with a name that
Android recognizes as being of that particular
configuration.
• So, if we want to have a different layout for
landscape and portrait, we would create a
folder called layout-land in the res folder and
place our specially designed layout within it.
• When the device is in the portrait position, the
regular layout from the layout folder would be
used, and when it is in landscape, the layout
from the layout-land folder would be used.
• If we are designing for screens with different pixel densities, we
can place XML layouts into folders with names like these:
• The layout-ldpi layout for low DPI devices
• The layout-mdpi layout for medium DPI devices
• The layout-hdpi layout for high DPI devices
• The layout-xhdpi layout for extra high DPI devices
• The layout-xxhdpi layout for extra, extra high DPI devices
• The layout-xxxhdpi layout for extra, extra, extra high DPI devices
• The layout-nodpi layout for devices with a DPI that you have not
otherwise catered for
• The layout-tvdpi layout for TVs
Using configuration qualifiers – mini app

• Create a new project and call it Configuration


Qualifiers and follow the next steps:
• Right-click on the res folder in the project
explorer and navigate to New | Android
resource directory. Type layout-land and click
on OK.
• In the layout_main.xml file, change the text of
the default TextView widget from Hello world
to Hello portrait!.

• Right-click on the layout-land folder and


navigate to New | Layout resource. Name the
file layout_main.xml. Add a single Plain
TextView widget and change the text property
to Hello landscape!.
• Run the app and rotate the device between
landscape and portrait orientations. Note that
the OS automatically uses the appropriate
version of layout_main.xml.
Fragments
• Fragments are reusable elements of an app
just like any class, but as mentioned
previously, they have special features, such as
the ability to load their own view as well as
having their very own lifecycle methods
Fragments have a lifecycle too
• We can set up and control Fragments very
much like we do Activities, by overriding the
appropriate lifecycle methods.
onCreate
• In the onCreate method, we can initialize
variables and do almost all the things we
would typically do in the Activity onCreate
method. The big exception to this is initializing
our UI.
onCreateView
• In this method, we will, as the name suggests,
get a reference to any of our UI widgets, set
up anonymous classes to listen for clicks, and
do more besides these tasks, as you will soon
see.
onAttach and onDetach
• These methods are called just before the
Fragment is actually put into use/taken out of
use.
onStart, onPause, and onStop
• In these methods, we can take certain actions
such as creating or deleting objects or saving
data, just like we did with their counterparts
that were based on Activity.
Managing Fragments with
FragmentManager
• The FragmentManager class is part of Activity.
We use it to initialize Fragment, add
Fragments to the Activities layout, and to end
Fragment.
• The highlighted code here shows how we used FragmentManager (which is already
a part of the Activity) that was passed in as an argument to create the pop-up
dialog:
• button.setOnClickListener(new View.OnClickListener() {
• @Override
• public void onClick(View v) {
• // Create a new DialogShowNote called dialog
• DialogShowNote dialog = new DialogShowNote();
• // Send the note via the sendNoteSelected method
• dialog.sendNoteSelected(mTempNote);
• // Create the dialog
• dialog.show(getFragmentManager(), "123");
• }
• });
• The second argument of the method call is an
ID for Fragment. We will see how to use
FragmentManager more extensively as well as
use the Fragment ID.
• FragmentManager does exactly what its name
suggests. What is important here is that
Activity only has one FragmentManager, but it
can take care of many fragments; just what we
need to have multiple behaviors and layouts
within a single app.
• FragmentManager also calls the various
lifecycle methods of the fragments it is
responsible for. This is distinct from the
Activity lifecycle methods that are called by
Android, yet closely related because
FragmentManager calls many of the Fragment
lifecycle methods in response to the Activity
lifecycle methods being called
Our first working Fragment mini app
• Create a new project called Simple Fragment using
the same default settings as always.
• Switch to activity_main.xml and delete the default
Hello world! widget. Now, make sure that the
RelativeLayout root is selected by clicking on it in the
Component Tree window. Change its id property to
fragmentHolder. We will now be able to get a
reference to this layout in our Java code, and as the
id property implies, we will be adding a Fragment to
it.
• Now, we will create a layout that will define
our Fragment's appearance. Right-click on the
layout folder and navigate to New | Layout
resource file. In the File name field, type
fragment_layout and click on OK.
• Add a single Button widget anywhere on the
layout and make its id property button.
• In the project explorer, right-click on the folder
that contains the MainActivity file. From the
context menu, navigate to New | Java class. In
the Name field, type SimpleFragment and click
on OK.
• In our new SimpleFragment class, change the
code to extend Fragment. As you type the code,
you will be asked to choose a specific Fragment
class to import, as shown in the next screenshot:
• Now, add a single String variable called
myString and a Button variable called
myButton as members and override the
onCreate method. Inside the onCreate
method, initialize myString to Hello from
SimpleFragment.
• public class SimpleFragment extends Fragment {
• // member variables accessible from anywhere in this fragment
• String myString;
• Button myButton;
• @Override
• public void onCreate(Bundle savedInstanceState){
• super.onCreate(savedInstanceState);
• myString = "Hello from SimpleFragment";
• }
• }
• In the previous code, we created a member
variable called myString, and then in the
onCreate method, we initialized it. This is very
much like we do for our previous apps when
we only use Activity. The difference, however,
is that we did not set the view or attempt to
get a reference to our Button member
variable, myButton
• When using Fragment, we need to do this in the onCreateView
method. Let's override this now and see how we set the view and get
a reference to our Button.
• Add this code to the SimpleFragment class after the onCreate method:
• @Override
• public View onCreateView(LayoutInflater inflater, ViewGroup
container, Bundle savedInstanceState) {
• View view = inflater.inflate (R.layout.fragment_layout, container,
false);
• myButton = (Button) view.findViewById(R.id.button);
• return view;
• }
• To understand the previous block of code, we
must first look at the onCreateView method
signature. First, note that the start of the
method states that it must return an object of
the type View:
• public View onCreateView...
• Next, we have the three arguments. Let's look at
the first two now:
• (LayoutInflater inflater, ViewGroup container,...
• We need LayoutInflater as we cannot call
setContentView because Fragment provides no
such method. In the body of onCreateView, we use
the inflate method of inflater to inflate our layout
contained in fragment_layout.xml and initialize view
(an object of the type View) with the result.
• We use container that was passed in to
onCreateView as an argument in the inflate
method. The container variable is a reference
to the layout in activity_ main.xml.
• It might seem obvious that activity_main.xml
is the containing layout, but as we will see
later in the chapter, the ViewGroup container
argument allows any Activity with any layout
to be the container for our fragment. This is
exceptionally flexible and makes our Fragment
code reusable to a significant extent.
• The third argument that we pass into inflate is
false, which means that we don't want our
layout to be immediately added to the
containing layout. We will do this soon from
another part of the code.
• The third argument of onCreateView is Bundle
savedInstanceState, which is there to help us
maintain the data that our fragments hold.
• Now that we have an inflated layout contained
in view, we can use this to get a reference to
Button like this:
• myButton = (Button)
view.findViewById(R.id.button);
• And we can also use it as the return value to
the calling code, as required:
• return view;
• Now, we can add an anonymous class to listen
for clicks on our button in the usual manner. In
the onClick method, we display a pop-up Toast
message to demonstrate that everything is
working as expected. Add this code just before
the return statement in onCreateView as
highlighted in this next code:
• myButton = (Button) view.findViewById(R.id.button);
• myButton.setOnClickListener(new
View.OnClickListener() {
• @Override
• public void onClick(View v) {
• Toast.makeText(getActivity(),myString ,
Toast.LENGTH_SHORT).show();
• }
• });
• As a reminder, the getActivity() call that is
used as an argument in makeText gets a
reference to the Activity that contains
Fragment. This is required to display a Toast
message. We also used getActivity in our
classes based on FragmentDialog in the Note
To Self app.
• We can't run our mini app just yet, as it will
not work because there is one more step that
we need to complete. We need to create an
instance of SimpleFragment and initialize it
appropriately. This is where FragmentManager
will get introduced.
• This next code creates a new FragmentManager
by calling getFragmentManager. It creates a new
Fragment based on our SimpleFragment class by
using FragmentManager and passing in the ID of
the layout (within the Activity) that will hold it.
• Add this code in the onCreate method of
MainActivity.java just after the call to
setContentView:
• FragmentManager fManager = getFragmentManager();
• // Create a new fragment using the manager
• // Passing in the id of the layout to hold it
• Fragment frag = fManager.findFragmentById(R.id.fragmentHolder);
• // Check the fragment has not already been initialized
• if(frag == null){
• // Initialize the fragment based on our SimpleFragment
• frag = new SimpleFragment();
• fManager.beginTransaction()
• .add(R.id.fragmentHolder, frag)
• .commit();
• }
Fragment reality check
• So, what does this Fragment stuff really do for
us? Our first Fragment mini app would have
exactly the same appearance and functionality
had we not bothered with Fragment at all. In
fact, using Fragment has made the whole
thing more complicated! Why would we want
to do this?
• We know that Fragment or fragments can be
added to the layout of an Activity. We know
that a Fragment not only contains its own
layout (view), but also its very own code
(controller), which although hosted by Activity,
the Fragment is virtually independent.
• Our quick mini app only showed one Fragment
in action, but we could have an Activity that
hosts two or more fragments. We can then
effectively have two almost-independent
controllers displayed on a single screen.
• What is most useful about this, however, is
that when the Activity starts, we can detect
attributes of the device that our app is running
on, perhaps a phone or tablet or in the
portrait or landscape orientation. We can then
use this information to decide to display either
just one or two of our fragments
simultaneously.

You might also like