More Effective Layouts In past weeks, we've looked at ways to make more effective use of the presented display (e.g. elastic layouts, and separate layouts for portrait and landscape), as well as autogenerating custom widgets for selections. Problems Both of these are slightly lacking. So far, creating a separate landscape layout has required repeating the entire user interface (with potential pitfalls for major errors). This would be even worse if we wanted to take the same approach to the phone vs tablet problem (likely including significantly different code as well). Additionally, our generated widgets have been fine for list-oriented entries, but still ignores a basic design question: what if we want to present a popup, just to get some input (or inputs) from the user? We wouldn't necessarily want to combine such questions with the larger interface, right? (For example, suppose you want to send a message to someone. It isn't unheard of for an application to present you with a contact list that then entirely disappears) Solutions Basically, the two topics we want to discuss are fragments and dialogs; albeit in the opposite order. If we have time, we'll also get our first taste of minimal (organized) persistence. Dialogs We'll be using dialogs in cases where we theoretically could have just made a new Activity, but where that would be overkill (or where we don't really want to completely leave the current context). Though we can create our own custom dialogs, Android also provides several handy base types. Let's start off simple. AlertDialogs Make a new project, and let's get the skeleton of a basic layout down: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context="ca.brocku.efoxwell.a2017_fifthstage.mainactivity" android:orientation="vertical" > <Button android:text="simple" android:id="@+id/lerta" android:onclick="pushy"
<Button android:text="interesting" android:id="@+id/lertb" android:onclick="pushy" <Button android:text="feedback" android:id="@+id/lertc" android:onclick="pushy" <TextView android:id="@+id/feedback" <Button android:text="next!" android:id="@+id/enoughlerts" android:onclick="pushy" I figured I'd probably want four buttons, but we can change that. Also, yours doesn't need to look like mine. The AlertDialog is used to either present a notification, or to get minimal feedback. In all cases, it acts as a sort of a popup, as an alternative to the current Activity/mode (for this reason, you might want to use them sparingly. More often than not, you don't want your application to interrupt itself). Of course, we'll be using a switch statement to handle the button presses, so I'll focus on what's inside. Let's start simple. To simplify the creation of a new Dialog, we'll use a builder: AlertDialog.Builder firstdialog=new AlertDialog.Builder(this); firstdialog.settitle("attention!"); firstdialog.seticon(r.mipmap.ic_launcher); firstdialog.setmessage("note: this is a dialog. That is all."); firstdialog.show(); Well, that's... boring. Also, note that you have to press the 'back' button or click somewhere else to get out of it. You wouldn't commonly want an AlertDialog this simplistic. At the very least, you'd want some sort of acknowledgement button on it, and possibly a negative/cancel (e.g. if asking to proceed). We have up to three buttons available to us (positive, neutral, and negative), so let's use them all:
AlertDialog.Builder seconddialog=new AlertDialog.Builder(this); seconddialog.settitle("attention!"); //I no longer care about the icon seconddialog.setmessage("i'm Mr. Dialog! Look at me!"); seconddialog.setpositivebutton("ok", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int buttonid) { Toast.makeText(MainActivity.this,"Caaan dooo!",toast.length_short).show(); ); seconddialog.setnegativebutton("cancel", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int buttonid) { Toast.makeText(MainActivity.this, "Well, which is it? Square my shoulders or keep my head down?", Toast.LENGTH_SHORT).show(); ); seconddialog.setneutralbutton("(other)", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int buttonid) { Toast.makeText(MainActivity.this, "We talked about this! It's both!", Toast.LENGTH_SHORT).show(); ); seconddialog.show(); Of course, your text would presumably be different. Or not. Up to you. Note that it'd probably be more appropriate to use a single listener for all three (the buttonid will tell you which was pressed). Okay, so now we can show, for example, a disclaimer, or a permission/confirmation form. What else? Well, the dialog is already designed to contain a View. If we wanted anything more complicated than a single data entry, then we'd probably be best off just creating an entire custom dialog. Let's just programmatically create a single EditText to put into it, and retrieve the data (only) upon pressing OK. AlertDialog.Builder thirddialog=new AlertDialog.Builder(this); thirddialog.settitle("attention!"); thirddialog.seticon(r.mipmap.ic_launcher); thirddialog.setmessage("what's your favourite number?"); final EditText query=new EditText(this); query.sethint("like... 42?"); query.setinputtype(inputtype.type_class_number); thirddialog.setview(query); thirddialog.setpositivebutton("ok", new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int buttonid) { ((TextView)findViewById(R.id.feedback)).setText("Yep. "+ query.gettext()+" is neat."); ); thirddialog.setnegativebutton("cancel",
new DialogInterface.OnClickListener() { public void onclick(dialoginterface dialog, int buttonid) { return; ); thirddialog.show(); break; So now, we can easily get a bit of information back from the user whenever we want. Other premade dialogs There are plenty of other dialogs already built into Android. Some of the more esoteric ones include CharacterPickerDialog and ProgressDialog. But let's look at a pair of dialogs you might actually semi-commonly need. Time for another Activity. Just give its layout two buttons (one for this demonstration, and one to move on). I'm assuming the demo button is called chronology. Heck, I'll use the same for the Activity, itself. public class Chronology extends AppCompatActivity implements DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener{ @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_chronology); public void chronology(view v) { java.util.calendar today=java.util.calendar.getinstance(); new DatePickerDialog(Chronology.this, //context this, //listener today.get(java.util.calendar.year), today.get(java.util.calendar.month), today.get(java.util.calendar.day_of_month)).show(); public void ondateset(datepicker view, int year, int monthofyear, int dayofmonth) { java.util.calendar today=java.util.calendar.getinstance(); new TimePickerDialog(this, this, today.get(calendar.hour_of_day), today.get(calendar.minute), true //24-hour display? ).show(); public void ontimeset(timepicker view, int hour, int minute) { android.widget.toast.maketext(this,"neat.", Toast.LENGTH_SHORT).show(); public void nextactivity(view v) { //startactivity
Yeah, it doesn't actually do anything. But we can still see roughly how it works. Note that this isn't the same thing as having the user's list of contacts pop up for selecting. Contacts rely on a content provider, and we aren't there just yet. Fragments Okey dokey. No real segue here; just a flat introduction of the problem: as mentioned earlier, if I want to have different layouts according to different screen geometries, my choices (with the tools we've covered thus far) have been pretty limited. Basically, I need to recreate everything from scratch for each different type of view. How much of that is really necessary? Certainly, if we wanted the application to look different between its portrait and landscape orientations, we'd definitely need different XML layouts. But do we need complete copies of the layouts? For example, there is likely to be a great deal of overlap between them. Just for the sake of clarification, let's look at an example (which we won't actually be creating today). Thought experiment Let's say we wanted to make a graphing calculator. In a portrait view, we'd probably have the graphing area at the top, and the buttons at the bottom. In landscape, that would look squished, and borderline useless. Instead, we might prefer the buttons on the right, and the graphing area on the left (or vice versa). However, the graphing area itself is the same, irrespective of where it is, and the same goes for the buttons. So then, what's a Fragment? Well, this is one of those it makes more sense when you see it cases, but, in short, a Fragment is a selfcontained, pseudo-activity, that keeps its own interface to itself. The application can then have one or more Fragments simultaneously, and either display them together, or let you switch between them, or selectively only show some when there's enough room. Fragments have their own separate life cycles, not directly tied to those of the Activities. The Android developer page has a good perspective: You can combine multiple fragments in a single activity to build a multi-pane UI and reuse a fragment in multiple activities. You can think of a fragment as a modular section of an activity, which has its own lifecycle, receives its own input events, and which you can add or remove while the activity is running (sort of like a "sub activity" that you can reuse in different activities). Read more here: https://developer.android.com/guide/components/fragments.html
Life cycles A Fragment has its own life cycle, but that life cycle is still closely connected to its Activity's. For example, if you pause an Activity, it would be odd if the pieces within that Activity didn't also pause, right? For the sake of visualizing the connection between the two, consider the following image, stolen shamelessly from the Android developer page: The two major methods to override are oncreate() and oncreateview(). oncreate() should be self-explanatory by now, but oncreateview() might not be. Basically, because Fragments handle UIs a little differently from Activities (when they even have UIs at all), we'll have to build them slightly differently as well. In this case, we'll need to explicitly construct the UI, and then return that 'View' for incorporation into the Activity. If a Fragment doesn't need a UI, just return null. First example Let's start small for our first example, okay? In fact, let's start so small that it's effectively pointless. Let's make a single Fragment to fit within a single Activity, just to confirm that we can, k? You know the drill by now. Let's create a new Activity (I'm calling mine FraggyOne), and load it up from that second button we created for our last layout. For this example, I'm still going to create a layout for the Activity, but it will be pretty trivial, as its sole job will be to hold a Fragment. While I'm at it, I'll also create the new Fragment, FraggyOneFrag, and have Android studio create a layout for that for me. I won't have it create the other extras, though.
Our first Fragment: layout <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" tools:context="ca.brocku.efoxwell.a2017_fifthstage.fraggyonefrag" android:orientation="vertical"> <TextView android:text="things!" <TextView android:text="stuff!" <Button android:text="button!" android:onclick="thatsit" Our first Fragment: code public class FraggyOneFrag extends Fragment { @Override public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { // Inflate the layout for this fragment return inflater.inflate(r.layout.fragment_fraggy_one, container, false); Corresponding Activity: layout <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" > <fragment android:name="ca.brocku.efoxwell.a2017_fifthstage.fraggyonefrag" android:id="@+id/fragonefrag" tools:layout="@layout/fragment_fraggy_one" Corresponding Activity: code public class FraggyOne extends AppCompatActivity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_fraggyone); public void thatsit(view v) { startactivity(new Intent(this,MainActivity.class));
Of course, we'll be wanting to change that MainActivity.class to our actual next Activity later. In this case, we just had the Activity handle the button click because it was easier. But we can always get our Activity's application context, if necessary. Also note that, in the layout file for the Activity, there's an entry explicitly stating which layout to use. That doesn't really mean much, functionality-wise. Rather, it's a hint to Android Studio, so it knows how to preview the final appearance of the total Activity. That example was boring This is true. Let's move on to using two Fragments in the same Activity, but still displaying them both. Let's say we'll want them to be stacked if in portrait, and side-by-side in landscape. Second example For this one, create the new Activity, EfficientLayout, and let the button start it. We'll get to filling it out in a moment. First, some boilerplate. I'll create Fragments of EfficientFruitFrag and EfficientViewFrag, and let Android Studio provide the layout files for them. Let's start by putting both Fragments into the Activity's layout. Activity layout: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:name="ca.brocku.efoxwell.a2017_fifthstage.efficientfruitfrag" tools:context="ca.brocku.efoxwell.a2017_fifthstage.efficientlayout" android:orientation="vertical"> <fragment android:layout_height="0dip" android:layout_weight="4" android:id="@+id/effragfruit" tools:layout="@layout/fragment_efficient_fruit" <fragment android:layout_height="0dip" android:layout_weight="2" android:id="@+id/effragview" android:name="ca.brocku.efoxwell.a2017_fifthstage.efficientviewfrag" tools:layout="@layout/fragment_efficient_view" <Button android:layout_height="0dip" android:layout_weight="1" android:onclick="nexteroonie"
Activity layout, landscape version: For this one, we just need to create the new version, change the vertical to horizontal, and tweak the width/height properties a bit. Activity code: public class EfficientLayout extends AppCompatActivity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_efficient_layout); public void nexteroonie(view v) { //start the next activity Fruit list fragment, layout: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" tools:context=".efficientfruitfrag" android:orientation="vertical"> <ListView android:id="@+id/fruits_list" android:drawselectorontop="false" Fruit view fragment, layout: <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" tools:context=".efficientviewfrag" android:orientation="vertical"> <TextView android:text="what kind of fruit do you like?" android:id="@+id/selectedopt" So far, so good, right? Next, we'll want to populate the list, and have it change the TextView to indicate the selection. But, wait, that's in another Fragment, isn't it? Doesn't that make it difficult? Nope.
Fruit view fragment, code: public class EfficientFruitFrag extends Fragment { @Override public View oncreateview(layoutinflater inflater, ViewGroup container, Bundle savedinstancestate) { //First, let's get access to the Activity enclosing this Fragment Context c=getactivity().getapplicationcontext(); //Next, we need to build up the layout View vw=inflater.inflate(r.layout.fragment_efficient_fruit,container,false); final String[] fruits={"apple","pear","applepear","pear of Apples","Purple"; ListView flist=(listview)vw.findviewbyid(r.id.fruits_list); ArrayAdapter<String> adapter=new ArrayAdapter<>( c,android.r.layout.simple_list_item_1,fruits ); flist.setadapter(adapter); flist.setonitemclicklistener(new AdapterView.OnItemClickListener(){ public void onitemclick( AdapterView<?> parent, View v, int position, long id ) { TextView selected=(textview)getactivity().findviewbyid(r.id.selectedopt); selected.settext("i also like "+((TextView)v).getText().toString()); ); return vw; Give it a try, and you'll see that it works perfectly!... wait a minute... mostly perfectly? Can you spot the issue? If not, we can just fix it together. I don't like the portrait version Nah, me neither. As mentioned earlier, a big part of the reason to use Fragments is so we can not only reorganize them, but also separate them. Let's try tweaking just the portrait version of the Activity's layout: New Activity layout, portrait version: <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" tools:context="ca.brocku.efoxwell.a2017_fifthstage.efficientlayout" android:orientation="vertical"> <fragment android:layout_height="0dip" android:layout_weight="4"
android:id="@+id/effragfruit" android:name="ca.brocku.efoxwell.a2017_fifthstage.efficientfruitfrag" tools:layout="@layout/fragment_efficient_fruit" <Button android:layout_height="0dip" android:layout_weight="1" android:onclick="nexteroonie" Note: All we did was to remove the second fragment. Fruit list fragment, code change: All we need to do is to change the onitemclick method as follows: public void onitemclick(adapterview<?> parent, View v, int position, long id) { if (getresources().getconfiguration().orientation== Configuration.ORIENTATION_LANDSCAPE) { TextView selected=(textview)getactivity().findviewbyid(r.id.selectedopt); selected.settext("i also like "+((TextView)v).getText().toString()); else { Intent intent=new Intent(getActivity().getApplicationContext(), EfficientItemActivity.class); intent.putextra("item",((textview)v).gettext().tostring()); startactivity(intent); Basically, if we're in a landscape orientation (meaning the other fragment is already there), do the exact same thing as before. However, if we're in portrait, we'll need to start a new Activity. Wait, what new Activity? Guess we'd better make one! However, we do not need to make a new layout for this one! Fruit view activity, code: public class EfficientItemActivity extends AppCompatActivity { @Override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); if (getresources().getconfiguration().orientation== Configuration.ORIENTATION_LANDSCAPE) { finish(); return; setcontentview(r.layout.fragment_efficient_view); Bundle extras=getintent().getextras(); if (extras!=null) { String selected=extras.getstring("item"); TextView tv=(textview)findviewbyid(r.id.selectedopt); tv.settext("i, too, like "+selected);
It's worth noting that this topic is actually far larger than this example (big surprise, eh?). For example, we can create Fragments programmatically, pass information between them (e.g. via Bundles or articles), and even save them on a stack (similar to how Activities are pushed onto the Activity stack), so you can retain a fragment's state if you need to switch away for a moment. There's a very good read here: https://developer.android.com/training/basics/fragments/index.html However, what we've seen so far is a decent first endeavour. It's also worth mentioning some of the other types of Fragment provided by Android. For example, there's a DialogFragment, to let you do largely the same thing as dialogs. There's also a PreferenceFragment, which can attach to the Shared Preferences. We'll probably be talking a bit about those in a week or so.