Layout xml merge tag

If you have a section of layout that you wish to reuse, it can be added using the <include> tag. Typically the code you are including would need a root view e.g. LinearLayout. However, if that root view would result in redundant code e.g. a vertical LinearLayout before the <include> tag immediately followed by the LinearLayout inside the reusable code, then instead of providing a root view for your reusable code then use the <merge> tag.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <include layout="@layout/reusable_layout"/>

    <include layout="@layout/reusable_layout"/>

    <TextView android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:text="@string/hello"
              android:padding="10dp" />

    ...

</LinearLayout>

 

 

// This is the block of reusable code
<merge xmlns:android="http://schemas.android.com/apk/res/android"> // No need for a root view here if it is to be nested inside a ViewGroup anyway. Putting a vertical LinearLayout here would produce a redundant ViewGroup

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/add"/>

    <Button
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:text="@string/delete"/>

</merge>

 

Manipulating LiveData data in the ViewModel

Transformations.map takes the input of one LiveData, applies a transformation (a function), and outputs another LiveData.

class MyViewModel : ViewModel() {

    companion object {
        private const val DONE = 0L
        private const val ONE_SECOND = 1000L
        private const val COUNTDOWN_TIME = 6000L
    }

    // Timer value
    private val _currentTime = MutableLiveData<Long>()
    val currentTime: LiveData<Long>
        get() = _currentTime

    val currentTimeString = Transformations.map(currentTime,{time -> // This takes in the current timer value and applies a time format before outputting it. Because it is a MutableLiveData it can be observed in a layout xml via data binding
        DateUtils.formatElapsedTime(time)
    })

    private val timer: CountDownTimer

    init {
        timer = object : CountDownTimer(COUNTDOWN_TIME, ONE_SECOND) {
            override fun onTick(millisUntilFinished: Long) {
                _currentTime.value = millisUntilFinished/ ONE_SECOND
            }

            override fun onFinish() {
                _currentTime.value = DONE
                _eventGameFinish.value = true
            }
        }
        timer.start()
    }
}

 

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <data>
        <variable
            name="myViewModel"
            type="com.example.android.myapp.MyViewModel" />
    </data>

        <TextView
            android:id="@+id/timer_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="@{myViewModel.currentTimeString}" /> // Call the LiveData from the ViewModel

</layout>

For more complex operations you may need to use a SwitchMap or MediatorLiveData. Allows manipulation and combination of more than one LiveData.

Navigation Drawer (with Navigation component?)

Add dependency:

dependencies {
    ...
    implementation "com.google.android.material:material:$version_supportlib" // Add version number to build.gradle also
}

Make sure any Fragments you wish to navigate to have been added to the Navigation Design window.

Create a new menu (e.g. navdrawer_menu.xml) in the res/menu folder.

<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> // All of this can be done through the GUI also

    <item
        android:id="@+id/firstFragment"
        android:icon="@drawable/first"
        android:title="@string/first" />
    <item
        android:id="@+id/secondFragment"
        android:icon="@drawable/second"
        android:title="@string/second" />
</menu>

In your main Activity layout file:

<?xml version="1.0" encoding="utf-8"?><!--

<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    
    <androidx.drawerlayout.widget.DrawerLayout // Wrap the main ViewGroup in the DrawerLayout tag and give it an id
        android:id="@+id/drawerLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">
            <fragment
                android:id="@+id/myNavHostFragment"
                android:name="androidx.navigation.fragment.NavHostFragment"
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                app:defaultNavHost="true"
                app:navGraph="@navigation/navigation" />
        </LinearLayout>
        
        <com.google.android.material.navigation.NavigationView // Create the NavDrawer element and give it an id
            android:id="@+id/navView"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_gravity="start" // Aligns it to the left of the screen for LtR languages
            app:headerLayout="@layout/nav_header" // Point to a layout file to define a custom header for your menu
            app:menu="@menu/navdrawer_menu" /> // Point it at the menu resource we just created

    </androidx.drawerlayout.widget.DrawerLayout>

</layout>

And in the main Activity file:

import ...

class MainActivity : AppCompatActivity() {
    lateinit var drawerLayout: DrawerLayout
    lateinit var navController: NavController
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        @Suppress("UNUSED_VARIABLE")
        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this, R.layout.activity_main)
        navController = this.findNavController(R.id.myNavHostFragment)
        drawerLayout = binding.drawerLayout // Get the DrawerLayout
        NavigationUI.setupActionBarWithNavController(this, navController, drawerLayout) // Add the DrawerLayout to the ActionBar
        NavigationUI.setupWithNavController(binding.navView, navController) // Link the navigation view with the UI
    }

    override fun onSupportNavigateUp(): Boolean {
//        return navController.navigateUp() // Get rid of this return statement
        return NavigationUI.navigateUp(navController, drawerLayout) // Use this instead which includes the DrawerLayout
    }
}

Add overflow menu with Navigation component in Kotlin

Create a menu resource file in the res/menu folder:

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/aboutFragment"
        android:title="@string/about" />

</menu>

In the relevant Activity/Fragment:

import ...

class TitleFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val binding: FragmentTitleBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_title, container, false)
        setHasOptionsMenu(true) // State that we want an overflow menu
        return binding.root
    }

    override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { // Override onCreateOptionsMenu
        super.onCreateOptionsMenu(menu, inflater)
        inflater?.inflate(R.menu.overflow_menu, menu) // Feed in the xml resource you created
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean { // Override onOptionsItemSelected 
        return NavigationUI.onNavDestinationSelected(item!!, view!!.findNavController()) // Use the NavigationUI class to determine if you clicked an item in your list
                || super.onOptionsItemSelected(item) // If not, defer to super
    }
}

 

Adding Navigation to an Android Project

Adding the Navigation component to an Android project in Kotlin

buildscript {
    ext {
        ...
        version_navigation = "1.0.0-beta01" // Update based on version at https://developer.android.com/jetpack/androidx/releases/navigation
    }
    ...
}
dependencies {
    ...
    implementation "android.arch.navigation:navigation-fragment-ktx:$version_navigation"
    implementation "android.arch.navigation:navigation-ui-ktx:$version_navigation"
}
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

        <LinearLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:orientation="vertical">

                <fragment
                    android:id="@+id/myNavHostFragment" // ID for Navigation fragment
                    android:name="androidx.navigation.fragment.NavHostFragment" // Add this. Don't ask, just do it. This is the host for all the other fragments
                    android:layout_width="match_parent"
                    android:layout_height="match_parent"
                    app:navGraph="@navigation/navigation" // And this. Points to navigation.xml in res/navigation folder which is where the Navigation Graph lives
                    app:defaultNavHost="true" /> // And this. Allows Navigation component to control system back key
        </LinearLayout>

</layout>

Create a new Resource file called navigation.xml in the res/navigation folder and switch to the Design view. You can now pull in Activities and Fragments and link them together. To link the Navigation Graph action (the link between two destinations) to a UI component use the built-in setOnClickListener:

class TitleFragment : Fragment() {
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        val binding: FragmentTitleBinding = DataBindingUtil.inflate(
                inflater, R.layout.fragment_title, container, false
        )
        binding.playButton.setOnClickListener (
            Navigation.createNavigateOnClickListener(R.id.action_titleFragment_to_gameFragment) // Built-in onClickListener. The resource pointed to here is auto-generated when a link is drawn between the 2 Fragments in the Navigation Graph
        )
        return binding.root
    }
}

 

Create Fragments

Add fragments to your app.

Create a new class for our Fragment:

public class AppFragment extends Fragment {

    public AppFragment() { // Empty constructor
    }

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { // Essential
        View rootView = inflater.inflate(R.layout.our_fragment, container, false); // Use a custom layout file (see below)

        ImageView imageView = rootView.findViewById(R.id.ourImageView); // Here we find a View within the layout

        imageView.setImageResource(R.drawable.anImage); // And allocate an image to it

        return rootView;
    }
}

Our layout file for the Fragment, our_fragment.xml:

<?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"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">

    <ImageView
        android:id="@+id/ourImageView"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:contentDescription="An Image"
        tools:srcCompat="@drawable/anImage" /> // Placeholder image
</LinearLayout>

Create an instance of our Fragment in the Activity and use FragmentManager to add it to the layout for the Activity:

        AppFragment aFragment = new AppFragment(); // New instance of our custom Fragment

        FragmentManager fragmentManager = getSupportFragmentManager();

        fragmentManager.beginTransaction()
                .add(R.id.fragment_container, aFragment) // Adds the new fragment to an element of the Activity layout (see below)
                .commit();

Our layout file for the Activity, activity_main.xml:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.example.android.app.MainActivity">

    <LinearLayout
        android:id="@+id/main_activity_linear_layout"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <FrameLayout android:id="@+id/fragment_container" // View that will contain the Fragment
        android:layout_width="match_parent"
        android:layout_height="180dp"
        android:scaleType="centerInside"/>

    </LinearLayout>
</ScrollView>

Touch selector

Gives visual feedback when an item is pressed.

Start with an xml file to define a selector drawable. Right-click on res->New->Android Resource File, Resource type: Drawable, Root element: selector, then 'OK'. We've named this file 'list_item_selector.xml':

<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">

    <item android:drawable="@color/colorPrimaryLight" android:state_pressed="true" /> // Add all these states to cover the various selection possibilities
    <item android:drawable="@color/colorPrimaryLight" android:state_activated="true" />
    <item android:drawable="@color/colorPrimaryLight" android:state_selected="true" />

    <!-- This should have a default drawable color = "@android:color/background_light" --> // Default unselected state
    <item android:drawable="@android:color/background_light" />

</selector>

In the example of a list item in a RecyclerView, go to the xml layout file for the list item and add the following to the root layout:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@drawable/list_item_selector "> // Add the selector xml file as the background

   ...

</LinearLayout>

Responsive design

To cater for the wide variety of device specifications, we need to consider screen size and density.

These are the 5 most popular density buckets:

  • mdpi (medium) ~160dpi
  • hdpi (high) ~240dpi
  • xhdpi (extra high) ~320dpi
  • xxhdpi (extra extra high) ~ 480dpi
  • xxxhdpi (extra extra extra high) ~640dpi

Density-independent pixels (dp’s) largely overcome these variations in density by making an image of e.g. 48dp look approximately the same physical size on all screens. **Make touch targets 48dp at least** However, you should supply a range of variations of each image to cater for the difference in density e.g. 48px:72px:96px:144px:192px in their appropriate folders to avoid placing unnecessary load on the processor and distorting the image, through scaling. To read more about how to cater for these go here.

For different screen layouts/orientations we can define individual xml files to cater for the variation in available space. Like the density buckets, you can place your layout files in the appropriately named folder and Android will use the layout that corresponds to its current configuration.

At runtime, Android will check the configuration of the device and choose the appropriate resources from the relevant folders accordingly. This is why it is necessary for Android to destroy and recreate Activities on screen rotation, as all of the resources within it could be completely different. Examples of folder variations:
values-fr/ – values for the French language
values-fr-rCA/ – values for the French-Canadian dialect
layout-desk/ – device is docked
layout-stylus/ – for device screens with stylus input
drawable-xhdpi/ – drawable resources for a xhdpi density screen
layout-land/ – landscape orientation
layout-sw720dp/ – minimum smallest screen width the layout will apply to

An example resource directory structure:

res/
   layout/
      activity_main.xml
      detail_activity.xml
      list_item.xml
   layout-sw600dp/
      detail_activity.xml
      list_item.xml
   layout-sw720dp/
      list_item.xml

To create a smallest-width qualifier folder, right-click on res->New->Android Resource Directory, Resource type: layout, Available qualifiers: Smallest Screen Width, enter screen width (in dp), then ‘OK’. File names must be identical across folders to them to be overridden.

Custom styles and themes

To customise the appearance of multiple objects at once with consistent visuals you can use:

  • Themes – generate an app-wide definition of visual attributes
  • Styles – create a set of attributes which can be applied individually to multiple objects

In styles.xml:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar"> // Uses the light Android theme. Exclude '.Light.DarkActionBar' for dark theme
        <!-- Customize your theme here. -->
        <item name="colorPrimary">@color/colorPrimary</item> // Colors defined in the colors.xml file
        <item name="colorPrimaryDark">@color/colorPrimaryDark</item>
        <item name="colorAccent">@color/colorAccent</item>
    </style>


    <!-- Style for textViews that are displayed in a list on the main screen-->
    <style name="MainActivityLabelStyle"> // Custom name which is referenced in the layout .xml file for each object the styles are being applied to: style="@style/ActivityLabelStyle"
        <item name="android:layout_width">match_parent</item>
        <item name="android:layout_height">80dp</item>
        <item name="android:textColor">@android:color/black</item>
    </style>

    <style name="BoldMainActivityLabelStyle" parent="MainActivityLabelStyle"> // Style which inherits all of the MainActivityLabelStyle attributes and adds bold text
        <item name="android:textStyle">bold</item>
    </style>

</resources>

Include layouts

If creating multiple instances of the same layout it is good practice to save that layout code in its own file and instantiate it using the tag e.g. you are designing separate portrait and landscape layouts of the same content with groups of objects such as those in a table which will remain in an identical relative layout across both designs.

Given this original layout:

<?xml version="1.0" encoding="utf-8"?>
<layout>
<ScrollView xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.constraint.ConstraintLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:text="@string/passenger_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <TextView
            tools:text="@string/passenger_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

<!-- Start of section of code defining a group of objects in a fixed layout to be duplicated across portrait and landscape -->

        <ImageView
            android:id="@+id/leftRectangle"
            android:layout_width="60dp"
            android:layout_height="80dp"
            ... />

        <ImageView
            android:id="@+id/divider"
            android:background="@color/colorPrimaryLight"
            ... />

        <ImageView
            android:id="@+id/rightRectangle"
            android:layout_width="60dp"
            android:layout_height="80dp"
            ... />

        <TextView
            android:id="@+id/textViewOriginAirport"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewDestinationAirport"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <ImageView
            android:id="@+id/imagePlane"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewFlightCode"
            tools:text="@string/flight_code"
            android:layout_width="wrap_content"
            ... />

<!-- Endof section of code defining a group of objects -->

        <TextView
            android:id="@+id/textViewBoardingTimeLabel"
            android:text="@string/boarding_time_label"
            android:layout_width="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewBoardingTime"
            tools:text="@string/boarding_time"
            android:layout_width="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewDepartureTimeLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewDepartureTime"
            tools:text="@string/departure_time"
            android:layout_width="wrap_content"
            ... />

    </android.support.constraint.ConstraintLayout>
</ScrollView>
</layout>

The extracted code pasted into it separate layout file (name boarding_info.xml) would look like:

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android" // Very important to include these tags
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools">

    <android.support.constraint.ConstraintLayout // Very important to include these tags
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <ImageView
            android:id="@+id/leftRectangle"
            android:layout_width="60dp"
            android:layout_height="80dp"
            ... />

        <ImageView
            android:id="@+id/divider"
            android:background="@color/colorPrimaryLight"
            ... />

        <ImageView
            android:id="@+id/rightRectangle"
            android:layout_width="60dp"
            android:layout_height="80dp"
            ... />

        <TextView
            android:id="@+id/textViewOriginAirport"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewDestinationAirport"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <ImageView
            android:id="@+id/imagePlane"
            android:layout_height="wrap_content"
            android:layout_width="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewFlightCode"
            tools:text="@string/flight_code"
            android:layout_width="wrap_content"
            ... />

    </android.support.constraint.ConstraintLayout>

</layout>

And the original file would look like:

<layout>
<ScrollView xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.constraint.ConstraintLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:text="@string/passenger_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <TextView
            tools:text="@string/passenger_name"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <include
            android:id="@+id/boarding_info" // Essential for binding data
            layout="@layout/boarding_info" // Only essential attribute - others added on on aligning with surrounding elements
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewBoardingTimeLabel"
            android:text="@string/boarding_time_label"
            android:layout_width="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewBoardingTime"
            tools:text="@string/boarding_time"
            android:layout_width="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewDepartureTimeLabel"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            ... />

        <TextView
            android:id="@+id/textViewDepartureTime"
            tools:text="@string/departure_time"
            android:layout_width="wrap_content"
            ... />

    </android.support.constraint.ConstraintLayout>
</ScrollView>
</layout>

If binding to these layouts you will need to add in an extra reference to the include file e.g.

mBinding.textViewOriginAirport.setText("aString");

becomes

mBinding.boardingInfo.textViewOriginAirport.setText("aString"); // boardingInfo automatically generated from 'boarding_info' xml name

Landscape layouts

To efficiently utilise the space available in a landscape orientation, Android allows the defining of an alternative layout.

Create a new layout resource folder named 'layout-land' and copy your portrait xml layout file into it. Then simply edit the layout according to your preferences. If there are elements of your layout that will remain identical across portrait and landscape modes it makes sense to relocate them into their own layout files as outlined in this post.

Localisation or Internationalisation

The adaptation of a product or service to meet the needs of a particular language, culture or desired population’s “look-and-feel”.

1. Translation - you should always design your app in a way that can be easily translated to other languages. To do so, any text that you would expect to be translated like labels and titles and button descriptions should all be defined as a string resource in res/values/strings.xml. This allows you to create other versions of strings.xml for other languages. This is done by creating a new values folder with the pattern value-xx where xx can be the abbreviation of any language from the ISO 639 code list here, for example res/values-fr/strings.xml will contain the french version of the strings.xml file with all the strings translated from the default language to French. Label Strings you do not want to translate like so:

<string name="timeFormat" translatable="false">hh:mm a</string>

2. RTL Support - if you’re distributing to countries where right-to-left (RTL) scripts are used (like Arabic or Hebrew), you should consider implementing support for RTL layouts and text display and editing, to the extent possible. By using the Start and End instead of Left and Right you make it possible for Android to reverse elements when necessary (if supporting API 16 or less include the Left and Right attributes as well):

android:layout_marginStart
android:layout_marginEnd

When images with a directional context are used, allow Android to reflect them to maintain the relevance of the image:

<vector android:autoMirrored="true"> </vector>

For more info on localisation click here.

Accessibility

See the recommendations here.

The most important is TalkBack which is a pre-installed screen reader service provided by Google. It uses spoken feedback to describe the results of actions such as launching an app, and events such as notifications.

You should include the following in your xml layout files to describe all ImageViews, ImageButtons and all Checkboxes:

<ImageView android:contentDescription="@string/origin_label"/>

Others include:

  • Enable focus-based navigation which makes sure users can navigate your screen layouts using external hardware like Bluetooth keyboards
  • No audio-only feedback, which guarantees any audio feedback to always have a secondary feedback mechanism to support users who are deaf or hard of hearing

Data Binding Library

Links the UI to actual data without having to use findViewById for every item.

In build.gradle, add 'dataBinding.enabled = true':

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
    buildToolsVersion "28.0.3"
    ...
    dataBinding.enabled = true;
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation'com.android.support:appcompat-v7:28.0.0'
    implementation 'com.android.support.constraint:constraint-layout:1.1.2'
}

Create a plain old Java object to act as a placeholder for the data:

/**
 * BoardingPassInfo is a simple POJO that contains information about, you guessed it, a boarding
 * pass! Normally, it is best practice in Java to declare member variables as private and provide
 * getters, but we are leaving these fields public for ease of use.
 */
public class BoardingPassInfo {

    public String passengerName;
    public String flightCode;
    public String originCode;
    public String destCode;

    public Timestamp boardingTime;
    public Timestamp departureTime;
    public Timestamp arrivalTime;

    public String departureTerminal;
    public String departureGate;
    public String seatNumber;

    public int barCodeImageResource;

    public long getMinutesUntilBoarding() {
        long millisUntilBoarding = boardingTime.getTime() - System.currentTimeMillis();
        return TimeUnit.MILLISECONDS.toMinutes(millisUntilBoarding);
    }
}

Add layout as the root tag to the UI (in this case, activity_main.xml file). Android will automatically create a Binding class for any layout with these tags:

<?xml version="1.0" encoding="utf-8"?>
<layout>
<ScrollView xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/scroll"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <android.support.constraint.ConstraintLayout
        android:orientation="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <TextView
            android:text="@string/passenger_label"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
            
            ...

    </android.support.constraint.ConstraintLayout>
</ScrollView>
</layout>

In your Activity, create a Binding instance, and point the instance to the correct content view using DatabindingUtil:

public class MainActivity extends AppCompatActivity {

    ActivityMainBinding mBinding; // ActivityMainBinding is automatically generated based on the activity_main.xml file name

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mBinding = DataBindingUtil.setContentView(this, R.layout.activity_main); // Passes in the Activity and sets the content view to the correct layout file
        BoardingPassInfo boardingPass = generateBoardingPassInfo(); // Whatever method to produce an instance of your placeholder class populated with data
        displayBoardingPassInfo(boardingPass); // Passes the placeholder instance to the method below
    }

    private void displayBoardingPassInfo(BoardingPassInfo info) {

        mBinding.textViewPassengerName.setText(info.passengerName);
        mBinding.textViewFlightCode.setText(info.flightCode);
        mBinding.textViewOriginAirport.setText(info.originCode);
        mBinding.textViewDestinationAirport.setText(info.destCode);
        mBinding.textViewTerminal.setText(info.departureTerminal);
        mBinding.textViewGate.setText(info.departureGate);
        mBinding.textViewSeat.setText(info.seatNumber);

        SimpleDateFormat formatter = new SimpleDateFormat("hh:mm a", Locale.getDefault());
        String boardingTime = formatter.format(info.boardingTime);
        mBinding.textViewBoardingTime.setText(boardingTime);
        String departureTime = formatter.format(info.departureTime);
        mBinding.textViewDepartureTime.setText(departureTime);
        String arrivalTime = formatter.format(info.arrivalTime);
        mBinding.textViewArrivalTime.setText(arrivalTime);

        long totalMinutesUntilBoarding = info.getMinutesUntilBoarding();
        long hoursUntilBoarding = TimeUnit.MINUTES.toHours(totalMinutesUntilBoarding);
        long minutesMinusHoursUntilBoarding = totalMinutesUntilBoarding - (hoursUntilBoarding*60);
        String hoursAndMinutesUntilBoarding = hoursUntilBoarding + ":" + minutesMinusHoursUntilBoarding;
        mBinding.textViewBoardingInCountdown.setText(hoursAndMinutesUntilBoarding);
    }
}

ud851-Exercises-student\Lesson11-Completeing-The-UI\T11.02

Quantity Strings (plurals)

When wanting to generate Strings that accommodate a range of numerical values, we need a way of determining the correct syntax according to the language. For example, in English we refer to ‘zero books’, ‘one book’ and ‘two (or more) books’. Android supports a set of options to cater for these variations, using the values: zero, one, two, few, many, other.

Methods such as getQuantityString can be used in conjunction with these options to provide the String you need to produce proper grammar. These are used exclusively for plurals and not for e.g. adding numerics to a String for information purposes: Inbox(12). Do not use quantity strings as a substitute for ‘if’ logic, as other languages handle grammatical rules differently.

It’s often possible to avoid quantity strings by using quantity-neutral formulations such as “Books: 1”. This makes your life and your translators’ lives easier, if it’s an acceptable style for your application.

The plurals can be stored in any xml resource file. It is the plurals element's name which Android identifies. The resource name is R.plurals.plural_name:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals
        name="plural_name">
        <item
            quantity=["zero" | "one" | "two" | "few" | "many" | "other"]
            >text_string</item>
    </plurals>
</resources>

As a working example:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <plurals name="numberOfSongsAvailable">
        <!--
             As a developer, you should always supply "one" and "other"
             strings. Your translators will know which strings are actually
             needed for their language. Always include %d in "one" because
             translators will need to use %d for languages where "one"
             doesn't mean 1.
          -->
        <item quantity="one">%d song found.</item>
        <item quantity="other">%d songs found.</item>
    </plurals>
</resources>

Associated Java:

int count = getNumberOfSongsAvailable();
Resources res = getResources();
String songsFound = res.getQuantityString(R.plurals.numberOfSongsAvailable, count, count);

When using the getQuantityString() method, you need to pass the count twice if your string includes string formatting with a number. For example, for the string %d songs found, the first count parameter selects the appropriate plural string and the second count parameter is inserted into the %d placeholder. If your plural strings do not include string formatting, you don't need to pass the third parameter to getQuantityString.

PreferenceChangeListener

The differences between PreferenceChangeListener and SharedPreferenceChangeListener are:

  • SharedPreferenceChangeListener is triggered after any value is saved to the SharedPreferences file.
  • PreferenceChangeListener is triggered before a value is saved to the SharedPreferences file. Because of this, it can prevent an invalid update to a preference.
  • PreferenceChangeListeners are also attached to a single preference.

So it can be used as follows:

  • User updates a preference.
  • PreferenceChangeListener triggered for that preference e.g. to validate input.
  • The new value is saved to the SharedPreference file.
  • onSharedPreferenceChanged listeners are triggered.

Implement Preference.OnPreferenceChangeListener:

public class SettingsFragment extends PreferenceFragmentCompat implements OnSharedPreferenceChangeListener, Preference.OnPreferenceChangeListener

In the override for onCreatePreferences attach the listener to the chosen Preference:

@Override
public void onCreatePreferences(Bundle bundle, String s) {
         /* Other preference setup code code */
        //...
        Preference preference = findPreference(getString(R.string.pref_xxxx_key)); // Key for the Preference to watch. Customise
        preference.setOnPreferenceChangeListener(this); // Attach the listener to the Preference
}

In this example we will use the onPreferenceChange override to validate the input and restrict it to a number between 0.1 and 3:

@Override
public boolean onPreferenceChange(Preference preference, Object newValue) {
   Toast error = Toast.makeText(getContext(), "Please select a number between 0.1 and 3", Toast.LENGTH_SHORT); // Error Toast if input invalid

   String xxxxKey = getString(R.string.pref_xxxx_key);
   if (preference.getKey().equals(xxxxKey)) {
       String stringInput = (String) newValue; // Cast the Object o a String
       try {
           float input = Float.parseFloat(stringInput); // Try converting to a float
           if (input > 3 || input <= 0) { // If outside the accepted range...
               error.show(); // ...show the error Toast...
               return false; // ...and fail to register the new change
           }
       } catch (NumberFormatException nfe) { // If cannot convert to float then throw an exception
           error.show();
           return false;
       }
   }
   return true; // Otherwise accept the value
}

ud851-Exercises-student\Lesson06-Visualizer-Preferences\T06.10

EditTextPreference

Typical xml code for an edit text preference entry.

    <EditTextPreference
        android:defaultValue="@string/pref_edit_text_default" // Default value for preference
        android:key="@string/pref_edit_text_key" // Key value for preference
        android:title="@string/pref_edit_text_label" /> // Title that appears in preference field

Setting Preference Summary/Label for ListPreference

ListPreference items in Preference screens do not automatically provide a label showing their current value. You can create labels for all list Preference items as so:

In the Settings Fragment onCreatePreferences override, get the Shared Preferences and the Preference screen. Then iterate through all of the Preferences in the screen, checking whether they are tickboxes. Any that aren't will have their value stored and passed to a setPreferenceSummary function along with the Preference itself:

    @Override
    public void onCreatePreferences(Bundle bundle, String s) {

        addPreferencesFromResource(R.xml.pref_xxxxx); // Reference to xml resource
        SharedPreferences sharedPreferences = getPreferenceScreen().getSharedPreferences(); // Retrieve the Shared Preferences from the Preference screen
        PreferenceScreen preferenceScreen = getPreferenceScreen(); // Retrieve the Preference screen
        int count = preferenceScreen.getPreferenceCount(); // The number of Preferences within the screen

        for (int i = 0; i < count; i++ ) { // For each Preference...
            Preference p = preferenceScreen.getPreference(i); // ...retrieve the Preference...
            if (!(p instanceof CheckBoxPreference)) { // ...make sure it isn't a checkbox...
                String value = sharedPreferences.getString(p.getKey(), ""); // ...then retrieve its value (can't use getString on a Boolean, so another reason to exclude tickboxes)...
                setPreferenceSummary(p, value); // ...and send the Preference and its value to the setPreferenceSummary function
            }
        }
    }

The setPreferenceSummary function is as follows:

    private void setPreferenceSummary(Preference preference, String value) {
        if (preference instanceof ListPreference) { // If it is a ListPreference...
            ListPreference listPreference = (ListPreference) preference; // ...cast it to an object of type ListPreference...
            int prefIndex = listPreference.findIndexOfValue(value); // ...retrieve the index of that value...
            if (prefIndex >= 0) {
                listPreference.setSummary(listPreference.getEntries()[prefIndex]); // ...and get the corresponding label for it, setting it as the Preference Summary
            }
        } else if (preference instanceof EditTextPreference) {
            // For EditTextPreferences, set the summary to the value's simple string representation.
            preference.setSummary(value);
        }
    }

Implement an OnSharedPreferenceChangeListener to make sure changes are implemented without having to force an onCreate (e.g. device rotation). Within the required onSharedPreferenceChanged override, again check to see if the preference is not a checkbox, and if so send the preference to the setPreferenceSummary function along with its value:

public class SettingsFragment extends PreferenceFragmentCompat implements OnSharedPreferenceChangeListener {

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        Preference preference = findPreference(key);
        if (preference != null) {
            if (!(preference instanceof CheckBoxPreference)) {
                String value = sharedPreferences.getString(preference.getKey(), "");
                setPreferenceSummary(preference, value);
            }
        }
    }
}

Register and unregister the OnSharedPreferenceChangeListener with the following overrides:

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
    }

ud851-Exercises-student\Lesson06-Visualizer-Preferences\T06.08

ListPreference

Typical xml code for list preference entry (using colours as dummy data).

    <ListPreference
        android:defaultValue="@bool/pref_list_default" // Default value from list
        android:key="@string/pref_list_key" // Key name to identify preference
        android:entries="@string/pref_list_entries" // Array with all the option labels
        android:entryValues="@string/pref_list_entry_values" // Array with all the values
        android:title="@string/pref_list_label" /> // Label for preference

In strings.xml (note that keys and values should always be 'translatable: false'):

    <string name="pref_list_label">Colour</string>
    <string name="pref_list_label_red">Red</string>
    <string name="pref_list_label_blue">Blue</string>
    <string name="pref_list_label_green">Green</string>
    <string name="pref_list_value_red" translatable="false">red</string>
    <string name="pref_list_value_blue" translatable="false">blue</string>
    <string name="pref_list_value_green" translatable="false">green</string>
    <string name="pref_list_default" translatable="false">red</string>
    <string name="pref_list_key" translatable="false">color</string>

In arrays.xml:

<resources>
    <array name="pref_list_entries">
        <item>@string/pref_color_label_red</item>
        <item>@string/pref_color_label_blue</item>
        <item>@string/pref_color_label_green</item>
    </array>
    <array name="pref_list_entry_values">
        <item>@string/pref_color_value_red</item>
        <item>@string/pref_color_value_blue</item>
        <item>@string/pref_color_value_green</item>
    </array>
</resources>

CheckBoxPreference

Typical xml code for checkbox preference entry.

    <CheckBoxPreference
        android:defaultValue="@bool/pref_show_text_default" // Default Boolean value: true (ticked) or false (unticked)
        android:key="@string/pref_show_text_key" // Key name to identify preference
        android:summaryOff="@string/pref_show_false" // Text to show if unticked
        android:summaryOn="@string/pref_show_true" // Text to show if ticked
        android:title="@string/pref_show_text_label" /> // Label for preference

Settings or Preferences – Making Changes In App

How to make user changes to options within the Settings Fragment affect changes in the app.

In the Activity call a function to setup shared preferences, then create that function. We will also link the shared preference object with the onSharedPreferenceChangeListener using .registerOnSharedPreferenceChangeListener:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xxxxx);
        mTargetView =  findViewById(R.id.activity_xxxxx); // View to be affected by the preference change
        setupSharedPreferences(); // Call custom function to set up preferences
    }

    private void setupSharedPreferences() {
        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this); // Get a reference to the default shared preferences from the PreferenceManager class
        mTargetView.setShowText(sharedPreferences.getBoolean(getString(R.string.pref_xxxxx), getResources().getBoolean(R.bool.pref_xxxxx_default))); // Get the value of a preference and use it to call a function (in this case, the custom function mTargetView.setShowText) which will change behaviour in the app
        sharedPreferences.registerOnSharedPreferenceChangeListener(this); // We can use 'this' as context as the Activity will implement OnSharedPreferenceChangeListener (see next)
    }

To make sure the Activity implements the change without having to force an onCreate (e.g.rotate device) implement an OnSharedPreferenceChangeListener within the relevant Activity:

public class xxxxxActivity extends AppCompatActivity implements SharedPreferences.OnSharedPreferenceChangeListener {

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { // This function already passes us a reference to the sharedPreferences and key, so we don't need to use the PreferenceManager
        if (key.equals(getString(R.string.pref_show_bass_key))) {
            mTargetView.setShowBass(sharedPreferences.getBoolean(key, getResources().getBoolean(R.bool.pref_show_text_default)));
        }
    }
}

Unregister the OnSharedPreferenceChangeListener when the Activity is shut down using .unregisterOnSharedPreferenceChangeListener:

    @Override
    protected void onDestroy() {
        super.onDestroy();
        PreferenceManager.getDefaultSharedPreferences(this).unregisterOnSharedPreferenceChangeListener(this);
    }

ud851-Exercises-student\Lesson06-Visualizer-Preferences\T06.05

Settings or Preferences Fragment

How to set up a settings Activity with a PreferenceFragment.

Create an Activity named SettingsActivity (including the activity_settings.xml layout file) with the following code:

public class SettingsActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_settings);
        ActionBar actionBar = this.getSupportActionBar();

        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true); // Enables the up button in the Action Bar
        }
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        int id = item.getItemId();
        if (id == android.R.id.home) {
            NavUtils.navigateUpFromSameTask(this); // Navigates up to parent activity (add supporting code to AndroidManifest.xml) OR...
            onBackPressed(); // ...replicates the function of the Back button (i.e. returns to last Activity) DO NOT ADD BOTH
        }
        return super.onOptionsItemSelected(item);
    }
}

To construct a menu with the option to enter the Settings fragment, create a new resource folder called 'menu' (if it doesn't already exist) and create 'xxxxx_menu.xml' (customise) as follows:

<?xml version="1.0" encoding="utf-8"?>

<menu xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/action_settings" // Rename the id as necessary
     android:title="Settings" // Text that will appear in the menu listing - move to strings.xml
     android:orderInCategory="100" // Order of item in menu
     app:showAsAction="never" /> // Determine whether it will appear in the action bar (if room) as opposed to the dropdown menu

</menu>

Add the following code to your Activity to add the menu:

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.xxxxx_menu, menu); // Customise according to xml name
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if(item.getItemId() == R.id.action_settings) {
            Intent startSettingsActivity = new Intent(this, SettingsActivity.class);
            startActivity(startSettingsActivity);
            return true; // Deliver explicit Intent to start Settings Activity on click
        }
        return super.onOptionsItemSelected(item);
    }

Within the AndroidManifest.xml file, make the SettingsActivity a child of the main activity (DO NOT do this if you intend to access your Settings screen from multiple Activities as this will lock it to one parent Activity. Instead, leave out lines 12-15 and use the onBackPressed() option is your Settings Activity) and also make the main activity launch mode 'singleTop' to prevent the Activity being remade on returning:

        <activity
         android:name=".xxxxxActivity" // Customise
         android:launchMode="singleTop"> // Stops parent activity being remade on exiting Settings
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity // Register
         android:name=".SettingsActivity"
         android:label="Settings" // ADD THESE LINES. Move text value to strings.xml
         android:parentActivityName=".xxxxxActivity"> // ADD THESE LINES ONLY IF NAVIGATING TO PARENT. Customise
            <meta-data // ADD THESE LINES ONLY IF NAVIGATING TO PARENT
             android:name="android.support.PARENT_ACTIVITY" // ADD THESE LINES ONLY IF NAVIGATING TO PARENT
             android:value=".xxxxxActivity" /> // ADD THESE LINES ONLY IF NAVIGATING TO PARENT. Customise
        </activity>

In the res -> xml folder create a file called pref_xxxxx.xml (customise) to define the options within the Settings Fragment:

<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">

    <CheckBoxPreference // In this example we have a tickbox option android:defaultValue="true" // Default value of true/ticked
     android:key="show_text" // The reference of the value which can be changed
     android:summaryOff="@string/hidden" // Text to be shown when tickbox is not checked
     android:summaryOn="@string/shown" // Text to be shown when tickbox is checked
     android:title="@string/show_text" // Title of option
     />

</PreferenceScreen>

Create a class called SettingsFragment as follows (may require the addition of: implementation 'com.android.support:preference-v7:28.0.0' to your gradle file):

public class SettingsFragment extends PreferenceFragmentCompat {
    @Override
    public void onCreatePreferences(Bundle bundle, String s) {
        addPreferencesFromResource(R.xml.pref_xxxxx); // Add the preferences xml to display the options (customise)
    }
}

Adjust the activity_settings.xml file to reflect a Fragment layout:

<?xml version="1.0" encoding="utf-8"?>
<fragment xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_settings"
android:name="xxx.xxxxx.xxx.xxxxx.SettingsFragment" // Customise xxxxx with app name e.g. android.com.example.app
android:layout_width="match_parent"
android:layout_height="match_parent">
</fragment>

Add a theme in the styles.xml file:

        <item name="preferenceTheme">@style/PreferenceThemeOverlay</item> // App will crash without this addition

To make user changes to the Settings Fragment take effect, follow this post: Settings or Preferences - Making Changes In App

ud851-Exercises-student\Lesson06-Visualizer-Preferences\T06.01 & 02

Request Permission

How to request a permission for your app

In the AndroidManifest.xml file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="com.example.android.datafrominternet">

    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name="com.example.android.datafrominternet.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>

                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>

</manifest>

Create Menu in ActionBar

Create a menu in the ActionBar. Can be in the main bar or under the menu icon.

Create 'main.xml' in 'menu' folder in 'res' if does not exist.
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <item android:id="@+id/action_search" // Add an item tag for each item in the menu
        android:title="@string/search"
        android:orderInCategory="1" // Order item will show in menu list
        app:showAsAction="ifRoom" // "ifRoom" will display menu item in ActionBar if room
        />

</menu>

Override onCreateOptionsMenu in Activity Java file.

@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
}

Override onOptionsItemSelectedin Activity Java file.

@Override
public boolean onOptionsItemSelected(MenuItem item) {
    int menuItem = item.getItemId();
    if (menuItem == R.id.action_search) { // Change 'action_search' to id of menu layout item
        Context context = MainActivity.this; // Change 'MainActivity' to current Activity
        CharSequence text = "Click handled"; // What happens when menu item is clicked
        int duration = Toast.LENGTH_SHORT; // What happens when menu item is clicked
        Toast toast = Toast.makeText(context, text, duration); // What happens when menu item is clicked
        toast.show(); // What happens when menu item is clicked
        return true;
    }
    return super.onOptionsItemSelected(item);
}

ud851-Exercises-student\Lesson02-GitHub-Repo-Search\T02.06-Exercise-AddPolish

FrameLayout with ScrollView and TextView

Example of Scrollview within FrameLayout xml

<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

    <ScrollView
		android:layout_width="match_parent"
		android:layout_height="wrap_content">
		
        <TextView
			android:id="@+id/tv_weather_data"
			android:layout_width="wrap_content"
			android:layout_height="wrap_content"
			android:padding="16dp"
			android:textSize="20sp" />
			
    </ScrollView>
	
</FrameLayout>

Adding An Activity, Up Navigation, Accessing With Explicit Intents, Passing Data

How to add an Activity to your app and navigate to it using an explicit Intent.

Create an empty Activity through the IDE. IDE will automatically add the essential code to the AndroidManifests.xml file. Make any desirable additions.

        <activity android:name="com.example.android.explicitintent.MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <activity android:name="com.example.android.explicitintent.ChildActivity"
            android:label="@string/action_settings" // Name at top of screen for Activity
            android:parentActivityName=".MainActivity"> // Make sure the back arrow functions properly by declaring parent activity
            <meta-data // To support Android 4.0 and lower
                android:name="android.support.PARENT_ACTIVITY"
                android:value=".MainActivity" />
        </activity>

In new Activity, enable Up navigation.

getSupportActionBar().setDisplayHomeAsUpEnabled(true); // Or getActionBar() on older versions before support library

In Parent Activity, set up an explicit Intent.

        private Button doSomethingCoolButton = (Button) findViewById(R.id.b_do_something_cool);
        private String text = "Send me!"; // Text to be passed to new Activity
        mDoSomethingCoolButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                Context context = MainActivity.this;

                /* This is the class that we want to start (and open) when the button is clicked. */
                Class destinationActivity = ChildActivity.class;

                /*
                 * Here, we create the Intent that will start the Activity we specified above in
                 * the destinationActivity variable. The constructor for an Intent also requires a
                 * context, which we stored in the variable named "context".
                 */
                Intent startChildActivityIntent = new Intent(context, destinationActivity);

                startChildActivityIntent.putExtra(Intent.EXTRA_TEXT, text); // Add text as extra data

                /*
                 * Once the Intent has been created, we can use Activity's method, "startActivity"
                 * to start the ChildActivity.
                 */
                startActivity(startChildActivityIntent);
            }
        });

In new Activity, access and use the extra data.

        mDisplayText = (TextView) findViewById(R.id.tv_display); // Find a TextView

        // Use the getIntent method to store the Intent that started this Activity in a variable
        Intent intent = getIntent();

        // Check if this Intent has the extra text we passed from MainActivity
        if (intent.hasExtra(Intent.EXTRA_TEXT)) {
            String enteredText = intent.getStringExtra(Intent.EXTRA_TEXT);
            mDisplayText.setText(enteredText); // Assign text to TextView
        }