Icons and strings

If you've made it this far, you actually have a fully working Android app! If we wanted to, this app is pretty much ready to publish! Let's add an icon and clean up the code a little bit: Right-click our res folder and select New → Image Asset Select "Launcher icon" from the dropdown menu at the top For "Asset type" (in the foreground layer tab), select "Image", and select the highest-resolution version of our grim reaper icon (the one from drawable-xxxhdpi folder in the zip file). Play around with the settings until you find a setup you like, then press "Next" and "Finish". There's a warning about overwriting some files. Those files are just the default icon, so don't worry. Look through the res/drawable folder and delete any remaining XML versions of the old default icon. What you need to delete depends on what options you selected. I only had two files to delete: ic_launcher_foreground.xml and ic_launcher_background.xml. Run the app and enjoy the beautiful new icon on your home screen! Now, let's say this app gets really popular (haha… 😒), what to do if people ask us to translate it? The texts we display are scattered all across the code, and we really can't start putting switch statements all over the place. The way to do language support in Android, is to place all strings in res/values/strings.xml. Android Studio also shows a warning whenever it detects that we display strings not in that file, and it can often extract the string for us automatically. Open activity_main.xml and click the orange "!" icon in the top-right corner of the layout designer. A panel with warnings appear. Expand the "Hardcoded text" warning, and click "Fix" at the bottom. In the dialog, give the string a name that makes it clear what this strings means even without any context. Then click OK. This is important because translators often don't see your app when translating it. This causes many bad translations when the same word can be used in different ways in the original language, but not the language being translated to. Example: English "Empty" is used to tell the user that a folder doesn't have anything in it, and for the text on the button that will delete everything inside the folder. In Norwegian, there are two different words for this: "Tom" og "Tøm". Google made this mistake in the Gmail app. To avoid these misunderstandings, use string resource names like action_empty, and label_is_empty, so translators can understand what the correct translation should be. The text in the layout is now replaced with @string/action_change_deadline or something like that, and if you look in res/values/strings.xml you should see this line: <string name="action_change_deadline">Change deadline</string> Replace any other strings you have in the layout using the same technique. When you're done, move on to MainActivity. Observe that most of the strings we display contain values we don't know until the app is run. How would we translate that to languages where the syntax is "Hours left is X"? The solution is use formatting strings! Let's start with "X hours left": Find the "X hours left" string (should be in updateUI()). Note: If you used Kotlin template strings, click the string, hit Alt+Enter and select "Convert to concatenated string". Change it to the string becomes "%d hours left". %d indicates to translators that we're going to insert a number. Press Alt+Enter and select "Extract strings resource". Again, use a proper resource name, like label_hours_left. The whole line now looks something like this: hoursLeftTextView.text = hoursLeft.toString() + getString(R.string.label_hours_left) That's better, but we're not quite there. If you place the cursor just before that last ) and press Ctrl+P, you'll see a popup telling you that getString() can take more arguments. These other arguments are the numbers we want inserted into the string. Put the number of hours left there, and remove it from the start of the string. Run the app. It should look exactly like last time you used it! Rinse and repeat until you run out of strings. If you want, you can translate the app by opening strings.xml, clicking "Open editor" at the end of the banner, clicking the globe 🌎 and selecting the language to translate to. If you ever end up translating a real app, please avoid automated translations, especially for smaller languages. They produce gibberish like "Velg søknader som kjører under system støvel." and you don't even know about it. Next: Displaying large lists (Kotlin)

Displaying large lists (Kotlin)

Remember I talked about having limited resources available in mobile apps? That means we can't just stuff all our data into a massive scroll view and hope it works. (Websites are notorious for doing just that. To make it work, your phone kills most non-essential processes when you open those websites.) The solution is to make it look like we have an infinite list of things to display. We do this by making views only for the list items currently onscreen. When you scroll down, the View that disappears off the top of the screen is re-used to display something else, and is added just below the bottom of the screen. In Android, RecyclerView does all the crazy logic needed to make this work efficiently. All we need to do is to provide views whenever RecyclerView needs them, and update them to display new items when asked to. There's a bit of code we need to write, but it's not too complicated. The performance gain is easily worth it if you have more than 15-20 items to display. Right-click on the app module in the project overview on the left and select New → Activity → Empty Activity. Name it ListActivity Source language should be Kotlin Check the box that says "Launcher Activity" (this makes a home screen icon the for activity) In our new layout file (mine is called activity_list.xml), drag a RecyclerView into the layout designer. When asked, yes, you add the RecyclerView library to Gradle. If it doesn't ask, add this in the dependencies section of Gradle scripts/build.gradle (Module: app): implementation 'com.android.support:recyclerview-v7:27.1.0' Add constraints to all edges of the screen. Remember, you add constraints by grabbing the handles on the sides of our view, and dragging "springs" to the corresponding edge of the screen. Remove any margins (using the Attributes panel), and set both width and height to match_constraint (if that doesn't work, try 0dp). Give the recycler view an ID, for example recyclerView. But what are we going to display?? "Item0/Item1/…" isn't particularly exiting… Right-click our res folder and select New Android Resrouce File. Set the following: File name: item_deadline.xml Resource type: Layout Root element: android.support.constraint.ConstraintLayout Source set: main Directory name: layout Re-create our scene with the text, grim reaper and computer guy. If you don't feel like doing it all over again, switch to "Text" view using the button at the bottom and paste this: <?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="140dp"> <TextView android:id="@+id/hoursLeftTextView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginBottom="8dp" android:layout_marginRight="8dp" android:text="Hello World!" android:textAppearance="@style/TextAppearance.AppCompat.Headline" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" /> <ImageView android:id="@+id/userImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="8dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toTopOf="@+id/hoursLeftTextView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent" app:srcCompat="@drawable/ic_computer_work" /> <ImageView android:id="@+id/reaperImageView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginEnd="54dp" android:layout_marginTop="8dp" app:layout_constraintBottom_toBottomOf="@+id/userImageView" app:layout_constraintEnd_toEndOf="@+id/userImageView" app:layout_constraintTop_toTopOf="@+id/userImageView" app:srcCompat="@drawable/ic_grim_reaper" /> </android.support.constraint.ConstraintLayout> In our code, we need to write three classes: Deadline – Contains the deadline date and name DeadlineViewHolder – Wrapper around an item view, takes care of switching out the contents whenever the view should display a different item. DeadlineAdapter – Create views and coordinate updates Let's start with the simplest one: Create a new Kotlin class called Deadline: data class Deadline(var title: String, val date: Date) {} Copy our getHoursLeft() method into this class. (Make sure you get the variable names right.) Hopefully not too bad? Let's try the next one: Create a new Kotlin class called DeadlineViewHolder. It should take one constructor parameter: view: View. Extend RecyclerView.ViewHolder. Remember Kotlin uses : instead of extends You need to call the constructor right away. It takes a View, wonder which one…? Before we finish the view holder, we need to understand how it will be used. So let's create the adapter which will use the view holder: Note: this section can be a bit overwhelming if you're not used to this kind of programming. If you get stuck, look at what variables and methods you have available in the code you're writing and what you need to end up with (the return value). Create a new Kotlin class called DeadlineAdapter. Make it extend RecyclerView.Adapter<DeadlineViewHolder> Hint: Remember the thing with the colon and the constructor call. There will be a red line telling you that you need to implement some methods. Click the text with the red line, hit Alt+Enter and select "Implement methods". This should give you three methods you need to implement, press OK to add them. As you see, we need to create views (more precisely, view holders), bind them (=make them display our items), and tell RecyclerView how many items we have. We don't have any items yet, so declare a mutable property (same as "field" in Java) called items and initialize it to an empty list. When this list changes, we need to tell RecyclerView about it. The easiest way to do that, is to create a setter. Kotlin has a special syntax for that: var items: List<Deadline> = emptyList() set(value) { notifyDataSetChanged() // Tell RecyclerView we got some new data field = value // Put the value we got into the field } Implement getItemCount. Hint: it's a one-liner. You should be able to figure it out on your own. onCreateViewHolder is more interesting. We need to parse item_deadline.xml and turn it into views somehow. In Android, this is called inflating the layout, and we do it using a layout inflater: // Fetch a layout inflater val layoutInflater = LayoutInflater.from(parent.context) // Read the layout for each item from the item_deadline file. // (Don't attach it, RecyclerView takes care of that) val view = layoutInflater.inflate(R.layout.item_deadline, parent, false) Hint: If you read the method header, you see that we should return a DeadlineViewHolder, not a View. Remember what argument the DeadlineViewHolder constructor takes? Hmm… The last method is onBindViewHolder. It gives us a viewHolder, and a position in the list. What we need to do is to fetch the corresponding deadline from the list of items, and give it to the view holder. Of course, this means that we need to add a method to the viewHolder that takes a Deadline object, let's name it bind: viewHolder.bind(deadline) Click the red bind method call, press Alt+Enter and "Create member function" to create the method. Still following? Let's finish up our DeadlineViewHolder class now that we know where it will be called: At the top, just below the class name, create references to our views. Kotlin's magic doesn't work here, so we need to use findViewById manually like this: val hoursLeftTextView: TextView = view.findViewById(R.id.hoursLeftTextView) Do the same for reaperImageView. We have our empty bind(deadline: Deadline) method. This method should do essentially the same thing as updateUI() in MainActivity, so copy the contents of that method in here. Update the code to use number of hours left from the Deadline object. Make our hoursLeftTextView display the title of the deadline too. Now we have everything we need to display a large list! All we need to do is to actually put some items in it and display it: Go to ListActivity.kt Declare a property deadlineAdapter = DeadlineAdapter() Set up the recyclerView: adapter = deadlineAdapter We want the views stacked vertically, the Linear Layout Manager does this by default: layoutManager = LinearLayoutManager(context) Add some deadlines to our adapter! I'm lazy, so I made an extra constructor in our Deadline class: constructor(title: String, month: Int, date: Int) : this(title, Date()) { this.date.month = month - 1 // Date does 0-indexing on month numbers… this.date.date = date // …but not on day of month this.date.hours = 23 this.date.minutes = 0 } If you happen to take the same courses as me, here are some deadlines you can use: val now = Date() deadlineAdapter.items = listOf( Deadline("Android-workshop", 4, 18), Deadline("Cogito-kurs", 4, 24), Deadline("DB øving 4", 4, 14), Deadline("PU Sprint 3", 4, 16), Deadline("MMI øving 5", 4, 30), Deadline("SL innlevering 3", 4, 30), Deadline("Eksamen KTN", 5, 15), Deadline("Eksamen SL", 5, 24), Deadline("Eksamen DB", 5, 30), Deadline("Eksamen MMI", 6, 5) ).filter { it.date.after(now) } // Don't display expired deadlines LF Congratulations, you made it to the end of this workshop! 🎉 Now what? You can try to: Sort deadlines by due date. Save deadlines in SharedPreferences. You need to write code to convert the deadlines into strings and then reconstruct them from those strings. An easy way to do so is to use the JsonObject (and JsonArray maybe) class. Add a "+" button so users can add new deadlines. This button would open a new activity (let's name it AddDeadlineActivity), where the user can specify date and title for the deadline. This activity would also save the new deadline in SharedPreferences. Use an Intent along with startActivityForResult() to open the activity. Override onActivityResult to update the items displayed in the recycler view when AddDeadlineActivity closes. From AddDeadlineActivity, you can use setResult() for indicate whether a deadline was added. Let users delete deadlines by tapping and holding the computer guy image. Create an interface containing only one function, onDeadlineDeleted(deadline:Deadline). Modify the adapter to take this interface as a constructor parameter. Use setOnLongClickListener in the code where you set up the view holder, and call onDeadlineDeleted from there. Make ListActivity implement the interface somehow, and write code to modify the saved deadlines (and reload the list of course). Maybe display a dialog to confirm deletion? Try AlertDialog.Builder Visit the GitHub repo and send pull requests for the code you wrote in these last few steps.

Sharing is caring – using Intents and the app bar (Kotlin)

Let's say we want to make it easy to share our deadline with friends. To do that, we'd like to have a button at the app bar at the top of the screen that opens the famous Share Menu when tapped. After selecting the app to use for sharing the deadline, that app is opened, ready to share the text: "I have a deadline in just X hours! Help me procrastinate" It turns out sharing text between apps in Android is really simple; just tell Android what we intend to do! To do so, we need to use an Intent. Go to MainActivity.kt Find the code that calculates the number of hours left, and select all of it. Press Ctrl + Alt + M to refactor it into a method. The created method should not take any parameters, and should return a long. Alternative approach, if you want to use fancy property access syntax: Declare a property val hoursLeft: Long On the next line, type get() { … }, and paste the hour calculation code there. To use the property, simply type hoursLeft. Create a new method to handle share button clicks. Inside this method, we create our Intent and give it to Android: public void onShareButtonClicked(MenuItem menuItem) { // We intend to send something Intent intent = new Intent(Intent.ACTION_SEND); // Here's what we are trying to send intent.putExtra(Intent.EXTRA_TEXT, put the text to share here); // Just plain old boring text. intent.setType("text/plain"); // Creates the panel that lets us choose app to share via Intent chooserIntent = Intent.createChooser(intent, "Share your deadline using…"); // Display the sharing panel startActivity(chooserIntent); } We can't use this method just yet – we need something to click on first! Let's make our share action icon. Right-click the res folder and select "New" → "Vector asset" Find and select the share icon. Set the name to ic_action_share (this is just a convention, you can give it whatever name you want). Set the color to white. Click Next, then Finish. Congratulations, you just created a vector icon that will look super-crisp on any device! Now include it in a menu: Right-click the res folder and select "New" → "Android resource file" Name the file main_menu and from the "Resource type" dropdown, select "Menu". Click OK to create the menu. Drag a "Menu item" into the menu. Set the ID of the item to action_share Title should be "Share" Icon should be the one we just created For showAsAction select "always" For onClick select our method onShareButtonClicked. Note: you might have to click "View all attributes" at the bottom to see this one. If you run the app now, you might notice that there's still no share button. That's because we haven't told Android to display our menu: In MainActivity, override the method onCreateOptionsMenu. The easiest way to do this is to just start typing the method name and accepting the suggestion. Use menuInflater.inflate() to read main_menu.xml into the Menu object, and return true to tell Android that we want to display it. Hint: The integer argument is supposed to be a resource ID. You might have noticed that we used R.layout.activity_main when referencing our layout at the top of onCreate? That was actually an integer referencing our layout. Where do you think we might find our menu? Now run the app and try sharing the deadline with your friends! Note: In early versions of Android, there was no such thing as an app bar. There was a title at the top, period. The stuff we have in the app bar today used to be in a menu that was displayed when the user pressed a physical menu button. Next: Icons and strings LF

Animation (Kotlin)

Finally, let’s do the animation! We need images of a CS student and the grim reaper. To speed things up, here are some ready-made images in the right dimensions. As you see, there’s a very specific folder structure here, telling Android Studio which images are for which display DPI (“display pixels per inch”). Again, Android Studio take care of much of this for you, and quasi-official tools like Android Asset Studio handle just about everything else. Download and unzip the folder into yourpojectlocation/app/src/main. The res folder in the zip file will be merged with the res folder already there, so you should end up with only one folder named res. Put an ImageView into our activity_main.xml layout. Make it display the computer worker guy (the srcCompat attribute selects image, use the … button to get a dialog where you can pick images from res). Give the ImageView an ID, let’s say userImageView. Note: Seems to be a bug in Android Studio making it fail to pick up the images you added. If this happens to you, File → Restart →  "Invalidate caches and restart" seems to solve it. You can also switch to Text mode and enter the image reference manually, see LF. Add constraints at the top of the screen, the top of our “X days left” text, and the right-hand edge of the screen. Put another ImageView into our layout. This time, select the grim reaper image. Give it an ID too, let’s say reaperImageView. Add constraints: connect the top of this image to the top of the other image, bottom to bottom, and right-side to right-side. This will make the images end up on top of each other. Use the Margin tool at the top of the Attributes panel to set the right-hand-side margin of the reaper image to 54dp. Note: there seems to be a bug when setting non-standard margins. If you can’t set a margin of 54, set it to one of the values from the dropdown and switch to Text view (button at bottom of designer) to update the margin to 54dp. If you run the app now, the layout should display the reaper standing almost on top of our poor computer guy. This is the “end” of our animation. What we’ll do next, is to throw the reaper offscreen on app startup, and display an animation moving the reaper towards the user. Of course, the more hours until the deadline, the farther away the reaper should stop! In onCreate, place the grim reaper outside the left side of the screen by setting its translationX to a large negative value (-1000f works). F or more serious work you should measure how far it is to the end of the screen and how wide the view is, instead of making stupid assumptions like this. At the end of our updateUI method, we want to start the animation. In Android, the easiest way to animate a view is to call its animate() method. Try animating the translationX to -10f * number of hours left, and set the duration to something like three seconds. Bonus points for fancier animation! Next: Sharing is caring (Kotlin) LF

Save the date! (Kotlin)

So do we put the deadline in outState and consider ourselves done? Well, we want to keep the deadline even if the entire phone is turned off, don’t we? Instead, we’ll use SharedPreferences. This is essentially a glorified way to save small amounts of data in key:value format on disk. SharedPreferences survive practically everything except a factory reset (and even then we can choose to have it backed up by Google). Let’s try it out: Create a field prefs for our preferences. We can't load it before onCreate, so we'll use lazy loading to avoid loading prefs until we actually use it (at which time we know onCreate has been called): val prefs by lazy { PreferenceManager.getDefaultSharedPreferences(applicationContext) } Note: If you know that the preferences are only going to be used in this particular Activity, you can use this.getPreferences(MODE_PRIVATE) instead. Preference manager's default shared preferences are shared across the entire app. In onDateSet, putthe deadline into prefs in millisecond format and commit the transaction: prefs.edit() .putLong("deadline", deadline.time) .apply(); Read the deadline in onCreate using prefs.getLong and update our deadline field. Tip: System.currentTimeMillis() returns the current time in millisecond format, and could be a good candidate for a default value. Fetching the deadline is nice and all, but shouldn't we display it too? While we're at it, what about making sure the time left is updated every time the app is presented onscreen, even if the activity is re-used? This is the famous "Android Activity Lifecycle", showing which methods of the Activity are called, and when it happens. In the lower left corner, we can see our onCreate() method, called once Android wants to create the activity. Whenever the app is about to become visible to the user, we can see that onStart() is called first. Override onStart(). Keep the call to super.onStart() Add a call to updateUI(). Run our app again. Now the correct deadline is displayed! Let’s finish up our app by adding the animation! Next: Animation LF

Setting and showing a deadline (Kotlin)

Let’s be honest here: dealing with date arithmetic is a pain, and working with Java or Kotlin does not exactly make things easier. In real apps, use JodaTime or JSR-310 to deal with dates. To keep things simple, we’ll use the old java.util.Date class you know from TDT4100 (“the Java course”) and a very dirty hack to calculate the number of hours left. Android Studio will complain, if you find it annoying just add @Suppress("DEPRECATION") at the top of our class. To ask for a date, the easiest solution is to display a DatePickerDialog. So, when the user taps “Change deadline”, display a date picker dialog. When the user taps OK, the date picker dialog tells us what the user selected (which means we need to listen to the date picker), and we update the deadline. Create a field called deadline, and initialize it using Date(). This field will hold the date selected by the user. When our button is clicked, create and display a date picker dialog. There are many ways to create the date picker, we’ll use the variant that expects the following: DatePickerDialog(context: Context, listener: OnDateSetListener, year: Int, month: Int, dayOfMonth: Int) A few hints: An Activity is a Context. MainActivity is an Activity. this works the same way you are used to from Java. OnDateSetListener is an interface you need to implement somehow. Use a lambda function or make MainActivity implement it. In Kotlin, you list everything you inherit from at the class declaration like this: class MyClass : SuperDuperClass(), BestInterfaceEver { The people who made Date used two-digit notation for year, so you need to add 1900 to the output of date.year. Don't confuse Date.day("day of week") and Date.date ("day of month") Remember to show the dialog. Run your app to test that it works before moving on. Hint: pickerDialog.show() By now, you should have an empty onDateSet method or lambda. Use it to update the deadline values. Important detail: remember to set time of day to 00:00 or 23:59 or whenever your deadlines are. Use a TimePickerDialog if you want to be fancy, it works just like the date picker dialog. Now we have the correct deadline, but we need to display it somehow. Let's apply our dirty hack for calculate the difference: To keep things (somewhat) tidy, create a method to update the screen: fun updateUI() { and call it at last line of onDateSet. Create a local Date object called now inside our new updateUI method. Get the time in milliseconds from both dates, and subtract. Convert to hours: val hoursLeft = TimeUnit.HOURS.convert(diff, TimeUnit.MILLISECONDS) // Use TimeUnit from java.util.concurrent, not from android.whatever. // (If you get this wrong, delete the import line at the top of the file and try again.) Update the hext to display the actual number of hours left. Optional: Display the deadline in the title at the top of the screen. title = "Whatever" and SimpleDateFormat.getDateTimeInstance().format(date: Date) are your friends. Run our app again. Now you can set the deadline, and the number of days left are updated! All done? Deadline works and everything? Turn your phone sideways, and see what happens… Next: When things go sideways – understanding lifecycle LF

Interactivity (Kotlin)

Even though we have a fancy layout now, the button doesn’t do anything. How can we write code that updates the text when the button is clicked? Actually, setContentView(…); in MainActivity returns nothing at all, so how can we even access our views from the code? The solution is to give every view an ID, and then do a lookup to get hold of them. Kotlin will do the lookup for us. Look in the Java version of this page if you want to do it manually. Let’s start with the text: In activity_main.xml, click the text view. Change the ID at the top of the Attributes panel to something like “hoursLeftTextView” or whatever you like. (If Studio asks to update references in XML code, answer Yes.) Next, update the text to “X hours left”: hoursLeftTextView.text = "X hours left" // Studio will import from kotlinx when you write this line. Run the app again. You should see that the text is now “X hours left”. Now, let’s change the time left every time we click the button! Write a method to handle button clicks in MainActivity, and move the setText line there: fun onChangeDeadlineClicked(viewThatWasClicked: View) { hoursLeftTextView.text = "X hours left" } In activity_main.xml, click our button and select onChangeDeadlineClicked for the onClick attribute. Now, Android knows what to do when the button is clicked. Run the app and try it out! Use Math.round(Math.random() * 100) to get a random number in [0, 100]. Display that number instead of X. Run our deadline app. It’s cool to have numbers changing all the time, but it’s not very useful. Can we count down toward a real deadline instead? Next: Setting and showing a deadline LF

Sharing is caring – using Intents and the app bar

Let's say we want to make it easy to share our deadline with friends. To do that, we'd like to have a button at the app bar at the top of the screen that opens the famous Share Menu when tapped. After selecting the app to use for sharing the deadline, that app is opened, ready to share the text: "I have a deadline in just X hours! Help me procrastinate" It turns out sharing text between apps in Android is really simple; just tell Android what we intend to do! To do so, we need to use an Intent. Go to MainActivity.java Find the code that calculates the number of hours left, and select all of it. Press Ctrl + Alt + M to refactor it into a method. The created method should not take any parameters, and should return a long. Create a new method to handle share button clicks. Inside this method, we create our Intent and give it to Android: public void onShareButtonClicked(MenuItem menuItem) { // We intend to send something Intent intent = new Intent(Intent.ACTION_SEND); // Here's what we are trying to send intent.putExtra(Intent.EXTRA_TEXT, put the text to share here); // Just plain old boring text. intent.setType("text/plain"); // Creates the panel that lets us choose app to share via Intent chooserIntent = Intent.createChooser(intent, "Share your deadline using…"); // Display the sharing panel startActivity(chooserIntent); } We can't use this method just yet – we need something to click on first! Let's make our share action icon. Right-click the res folder and select "New" → "Vector asset" Find and select the share icon. Set the name to ic_action_share (this is just a convention, you can give it whatever name you want). Set the color to white. Click Next, then Finish. Congratulations, you just created a vector icon that will look super-crisp on any device! Now include it in a menu: Right-click the res folder and select "New" → "Android resource file" Name the file main_menu and from the "Resource type" dropdown, select "Menu". Click OK to create the menu. Drag a "Menu item" into the menu. Set the ID of the item to action_share Title should be "Share" Icon should be the one we just created For showAsAction select "always" For onClick select our method onShareButtonClicked. Note: you might have to click "View all attributes" at the bottom to see this one. If you run the app now, you might notice that there's still no share button. That's because we haven't told Android to display our menu: In MainActivity, override the method onCreateOptionsMenu. The easiest way to do this is to just start typing the method name and accepting the suggestion. Use getMenuInflater().inflate to read main_menu.xml into the Menu object, and return true to tell Android that we want to display it. Hint: The integer argument is supposed to be a resource ID. You might have noticed that we used R.layout.activity_main when referencing our layout at the top of onCreate? That was actually an integer referencing our layout. Where do you think we might find our menu? Now run the app and try sharing the deadline with your friends! Note: In early versions of Android, there was no such thing as an app bar. There was a title at the top, period. The stuff we have in the app bar today used to be in a menu that was displayed when the user pressed a physical menu button. Next: Icons and strings LF

The anatomy of an Android project

In the project overview on the left-hand side, you’ll see quite a few folders and files. Here’s an overview: res/layout/activity_main.xml Here’s the layout for our main activity! We’ll get back to this in a moment. res/mipmap/ic_launcher This is our app’s icon, which will be displayed on the home screen (back in Android 1.x, this was called the “launcher”). It comes in many different sizes. Which size is used depends on the phone. If you create a high-resolution icon, Studio can generate scaled versions to make it automatically look good on any phone. res/values/styles.xml Controls how the app will look (colors, font sizes etc) Right now, we inherit our AppTheme from Theme.AppCompat.Light.DarkActionBar. Because dark apps look better, remove .Light.DarkActionBar to switch to the dark version. If you want, try to get rid of the awful default colors as well! java/no.hackerspace_ntnu.deadline/MainActivity This is where we write the Java/Kotlin code to “communicate” with the user. Long story short: we put views on the screen, and react when the user touches them. Right now, all we do is to tell Android where to find the activity_main.xml layout file. Notice: this class extends AppCompatActivity, which in turn extends Activity. These two classes contain lots of code to make our lives easier. The Activity class ships with every Android device out there, and is technically not part of our app. Google makes changes to this class in almost every new Android version, and after a few years it started getting really hard to keep track of what parts of Activity you could use and how it would behave on different devices. To make this easier, Google made AppCompat, a library (aka. pile of code) that makes almost everything work exactly the same way no matter what device our app is running on. AppCompatActivity is shipped with our app, and is the same no matter what device the app is running on. app/manifests/AndroidManifest.xml This file tells Android what our app is called, what it can do, which permissions it needs and so on. Notice how it uses our icon, app style and other resources from the `res` folder: @resource_type/resource_name The <activity android:name=”.MainActivity”> tag is very interesting. Here, we are telling Android how users can enter our app. In our case, we see that the app can be opened from the home screen (“launcher”). Gradle scripts Gradle is the build system that makes everything work together. Now that you know everything about how Android works (heh), let’s start coding our app! Next: Making a layout LF

Making a layout

Open app/res/layout/activity_main.xml. You’ll see the layout designer, with a “Hello World!” text at the center of a device screen. The text is an example of a so-called view. Technically, a view is just a class that extends View and draws something on the screen. A view whose purpose is to display other views is called a view group. The classic way of creating Android layouts is to use various ViewGroup classes, such as LinearLayout, FrameLayout and so on. Each of these perform some simple task, such as stacking views after each other horizontally or vertically. To create your layout, you just combine these in creative ways. A newer, fancier technique is the constraint-based layout. Here, you put everything you wish to display into a special ConstraintLayout view group, and then apply constraints telling the system how the views are supposed to be placed relative to each other. Think about them as springs holding the views in place. It's easier to understand if you just try it out: Click the “Hello World!” text. Notice how the four “springs” that show up, these are the view’s constraints. As you see, the constraints hold the view in the center of the screen. Let’s make the text bigger! On the right-hand side of Studio (the “Attributes” panel), find the textAppearance attribute, and select AppCompat.Display1 from the dropdown menu. You can also enter your own text size manually in the textSize attribute if you prefer. Find a Button in the left-hand overview of available views, and drag it onto the layout somewhere below the text. Create constraints: grab the handle at the top of the button and connect the spring to the bottom of the text. Connect the other sides to the edges of the screen. The button will end up centered horizontally, in the middle between the hello text and navigation bar(=the thing with back/home/recents buttons). Replace the text of the button to “Change deadline” by typing the new text into the text attribute in the attributes panel. (Yes, there are two attributes named text. Use the one without the wrench icon on it.) Now, run the app again to see your brand new layout in action! Protip: whenever the lightning bolt next to the play button is yellow, click the lightning bolt to see your changes “instantly”. Next: Interactivity (Java) or Interactivity (Kotlin) LF

Interactivity

Even though we have a fancy layout now, the button doesn’t do anything. How can we write code that updates the text when the button is clicked? Actually, setContentView(…); in MainActivity returns nothing at all, so how can we even access our views from the code? The solution is to give every view an ID, and then do a lookup to get hold of them. Let’s start with the text: In activity_main.xml, click the text view. Change the ID at the top of the Attributes panel to something like “hoursLeftTextView” or whatever you like. (If Studio asks to update references in XML code, answer Yes.) At the top of our class, declare a field TextView hoursLeftTextView;. In onCreate, just below setContentView, do the lookup: hoursLeftTextView = findViewById(R.id.hoursLeftTextView); Next, update the text to “X hours left”: hoursLeftTextView.setText("X hours left"); Note: in Kotlin, skip step 3 and 4. See Kotlin version for info. Run the app again. You should see that the text is now “X hours left”. Now, let’s change the time left every time we click the button! Write a method to handle button clicks in MainActivity, and move the setText line there: public void onChangeDeadlineClicked(View viewThatWasClicked) {    hoursLeftTextView.setText("X hours left"); } In activity_main.xml, click our button and select onChangeDeadlineClicked for the onClick attribute. Now, Android knows what to do when the button is clicked. Run the app and try it out! Use (int) (Math.random() * 100) to get a random number in [0, 100]. Display that number instead of X. Run our deadline app. It’s cool to have numbers changing all the time, but it’s not very useful. Can we count down toward a real deadline instead? Next: Setting and showing a deadline LF