Setting and showing a deadline

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 @SuppressWarnings("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 date field called deadline, and initialize it using new 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, OnDateSetListener listener, int year, int month, int dayOfMonth) A few hints: An Activity is a Context. OnDateSetListener is an interface you need to implement somehow. Use an anonymous class or make MainActivity implement it. The people who made Date liked two-digit notation for year, so you need to add 1900 to the output of date.getYear(), and subtract 1900 from the argument you pass to date.setYear(). Don't confuse date.getDay()("day of week") and date.getDate() ("day of month") Remember to show the dialog. Test that it works before moving on. Hint: datePicker.show(); By now, you should have an empty onDateSet method somewhere – either in MainActivity or inside an anonymous class in the DatePickerDialog constructor call. 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: void 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: long 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 text to display the actual number of hours left. Optional: Display the deadline in the title at the top of the screen. setTitle(String title); 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

When things go sideways – understanding lifecycle

Open the app, set a deadline, and turn the phone sideways so it switches to landscape mode. What happened?? Just turning the phone sideways made the app forget the deadline! Clearly this must be a bug in Android!? To understand what happens, we need to understand that mobile apps work differently from other apps. As Apple puts it: “Your app can be interrupted at any time. When an interruption occurs, your app should save the current state quickly and precisely so people can seamlessly continue where they left off when they return. “ An “interruption” usually happens when the user doesn’t use your app for a while, or when there’s little battery left and a phone call comes in. Both iOS and Android has plenty of tools to make it as easy as possible to save and restore the current state. You might have noticed already that our onCreate method has a parameter Bundle savedInstanceState. This bundle is used to restore our state. When it is time to save the state, Android will call onSaveInstanceState(Bundle outState). Whatever you put in outState will come back in savedInstanceState when the activity is recreated! So, back to our original question: why does the app forget the deadline when we rotate the phone?? Well… the people who made Android in the first place figured that since every activity should be able to save and restore their state automatically anyway: When the phone is rotated, they decided to simply save the state, throw away the activity and create a new one! This turned out to be a super-easy way to handle practically all configuration changes, including the tricky ones: resizing windows on Chromebooks split screen on tablets moving the activity to a TV screen (HDMI or Chromecast) changing font, scaling factor switching language Next: Save the date! (Kotlin version)

Save the date!

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: Declare a field SharedPreferences prefs; at the top of our activity class. In onCreate, load the file: prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); 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, convert the deadline into millisecond format, put it into prefs and commit the transaction: prefs.edit() .putLong("deadline", deadline.getTime()) .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 updateUI(). Run our app again. Now the correct deadline is displayed! Let’s finish up our app by adding the animation! Next: Animation LF

Animation

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 takes 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! Create a field for our ImageView reaperImageView and populate it using findViewById like we did earlier. In onCreate, place the reaper outside the left side of the screen by setting its translationX to a large negative value (-1000 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 -10 * number of hours left, and set the duration to something like three seconds. Bonus points for fancier animation! Next: Sharing is caring LF

Hello Android Studio!

Android Studio is where all Android development happens. It contains everything you need to develop, run, test and deploy Android apps. If you have worked with IntelliJ before, you’ll probably want to know that Android Studio actually is a fork of IntelliJ IDEA – so almost all keyboard shortcuts and menus are the same. If you’re new to IntelliJ and Studio, you need to learn two shortcuts right now: Ctrl + Shift + A: search for anything in any menu. Alt + Enter: Show suggestions to fix or improve whatever code you have highlighted. If you haven’t done so already, install Android Studio from d.android.com/studio. The installer asks some questions – if you’re not sure what to answer, just stick to the default options. Eventually, you’ll reach the screen titled “Welcome to Android Studio”. Optional: From the welcome screen, go to Configure ➡ Settings ➡ Editor ➡ General ➡ Auto import. Then, turn on “Add unambiguous imports on the fly” and “Optimize imports on the fly”. Now you can focus on writing code, and don’t worry about importing things. To get started: “Start a new Android Studio project”. Let’s call our app “Deadline”, and use “hackerspace-ntnu.no” for Company Domain. Decide if you want to use Kotlin or Java. To use Kotlin, check the box labeled "Include Kotlin support". On the next page, ensure that only “Phone and Tablet” is checked, and choose the oldest version of Android you want to support. For our project, the default is just fine. Then pick an "Empty activity". Stick to the defaults on the "Configure Activity" page. Eventually, your new app project will open in Studio. Click the green Play button near the top to run your app. If you have a real Android phone with you, turn on developer mode and USB debugging. Otherwise, click “Create a virtual device” and follow the instructions. Congratulations, you now have your brand new app on your phone! Next: The anatomy of an Android project