An app is composed of objects that can send messages to each other.
Most of the objects are provided by the iOS, example UIButton, UIAlertController, etc.
The remaining objects are created by us, for example, ViewController.
In iOS, apps are event-driven.
Portrait vs Landscape
In iOS, screen dimensions are measured in points. In older devices, 1 point = 1 pixel. Right now the plus devices had 1 point = 3 pixels.
To change the orientation of our app to landscape – Click on BullsEye in project navigator -> Select BullsEye in Target part -> In the general area, scroll down to the Device Orientation and uncheck portrait. Now run the app, the app stays in landscape mode even when we rotate the device.
Adding all UI Elements
Next, I added all the UI elements of the app on the screen using the objects of UIKit.
Open Main.Storyboard -> Utilities Panel -> Object Library -> Drag button to the controller
ViewController
A view controller manages a single screen or a portion of a screen. In this app, we have only one screen so only one view controller. View Controller has two parts:
Main.Storyboard where we add all the UI components to the view
ViewController.swift where we write code to handle those components
Adding action to the button
Open ViewController.Swift and add a function showAlert() with @IBAction prefix currently printing “Hello World!”. Then go back to Main.storyboard and click on the ViewController in the navigation area. Click on the button “Click Me”. Hold the button down and take the cursor to the ViewController. A drop-down will show with the method showAlert(), select that method. Now the button is connected to the action. When we run the app and click on the button “Hello World” is printed in the console.
Display Popup on clicking the button
Right now I just added the following code instead on print statement in the showAlert() method. We are going to learn what each statement means in the coming lessons.
let alert = UIAlertController(title: "Hello World!", message: "This is my first app", preferredStyle: .alert)
let action = UIAlertAction(title: "Awesome", style: .default, handler: nil)
alert.addAction(action)
present(alert, animated: true, completion: nil)
Now when I run the app and click on the button a pop up appears on the screen.
This is the screenshot of the assignment about adding another button –
Bull’s Eye app gives us a random value. And we need to take the slider to approximate value and click on the button to send our input. Then a score is calculated based on how close our guess was.
The second video focuses on making a list of things we will need to do to make our functioning Bull’s Eye App. Here’s my To-Do List for this app –
Slider for getting the guess from the user
Button for submitting the user’s guess
Pop up to show the score after submitting the user’s guess
Text showing the random number selected by the app
Text showing the rounds and scores
Reset button to reset score and round to zero and start a new game
I am back with another update. I am thinking of one/two weekly updates instead of every alternate day as that sounds more feasible.
The DelightfullDiscoveries blog has four major categories and other minor categories of posts. The four major ones are Experiment In Kitchen, Recipes, Books, and Thoughts. The minor ones are Awards, Travel, and InANutshell. Right now we fetch all the posts irrespective of their categories and display them. But I want to group posts by their category and display them. That’s why I decided to use TabLayout with each tab displaying posts of a particular category. This led to complete overhaul of the app developed till now. And I ran into some issues, so only added four tabs for now.
Prior to any changes, the app looked like this:
activity_main.xml
If you remember from Day 2 when we added the RecyclerView to the main activity along with the ProgressBar from Day 1. Today, I movedthe RecyclerView, the ProgressBar, and the TextView to another Layout file called post_list.xml with FrameLayout as the root element.
Changed the root layout of activity_main.xml to linear layout with the vertical orientation and added TabLayout and ViewPager.
Fragments and FragmentPagerAdapter
Created Four Fragment classes: EIKFragment, BooksFragment, ThoughtsFragment and AwardsFragment which extends Fragment class and implemented the onCreateView method.
And moved the code handling the RecyclerView, ProgressBar and the TextView from MainActivity to each fragment. So basically what MainActivity was doing before, now each of these Fragments will do the same.
The first difference is in the URLs to get the posts (now the posts are accessed by category). Added another method in NetworkUtils called getUrlFromCategoryId(int id) to get posts with category “id”. And call this method from the AsyncTask class’s background method.
Having a different Loader ID (POST_LOADER_ID) for each fragment is necessary otherwise the fragments will get confused. Suppose the loader ID is same for EIKFragment and BooksFragment, we will see EIK’s posts in BooksFragment instead of EIKFragment. EIKFragment will go into infinite loop displaying only the ProgressBar.
Created CategoryAdapter which extends the FragmentPagerAdapter. FragmentPagerAdapter. This makes sure that each page represents a Fragment and that Fragment’s data remains in the FragmentManager so that user can come back afterward. The drawback here is with an increase in the number of posts with each category, the amount of data to be persisted will increase for each category resulting in the use of huge memory. Later, I might switch to FragmentStatePagerAdapter which deletes the fragment as soon as we switch to another fragment saving only the necessary data.
Another improvement that I can do is to reuse the fragment. All the four fragments have the same code. The only difference is the loader id and the URL to access the data. I am working on that right now.
MainActivity
Removed the menu implementation for now. Deleted the overridden methods of interfaces PostAdapter.PostAdapterOnClickHandler and LoaderManager.LoaderCallbacks<Post[]>.
Added the code to handle TabLayout and ViewPager.
PostDetailActivity
While going through post JSON, I found a field URL in every post. Earlier, I was passing slug and using it to create the same URL which is already present in a post. Therefore, while parsing JSON, I now fetch the URL and store along with other data in a Post object. And this URL is passed as extra data now, whenever we click on a post in the RecyclerView.
No need to create the URL in PostDetailsActivity now, just use the incoming URL and display it in the WebView.
The app looks like this now:
Files Created
EIKFragment.java
BooksFragment.java
ThoughtsFragment.java
AwardsFragment.java
CategoryAdapter.java
post_list.xml
Files Modified
MainActivity.java
PostDetailActivity.java
activity_main.xml
DDJsonUtils.java
NetworkUtils.java
Today’s task took 6 Pomodoros, ie, 2 hrs, 30 minutes.
I am back with another update. Today, I deal with orientation changes for both the activities (MainActivity and PostDetailActivity).
MainActivity
Let’s take the MainActivity first. It consists of a RecyclerView which displays the titles of all the posts fetched from the website. When I rotate the screen, it triggers another call to the internet to fetch data. We can see clearly, this last call is unnecessary as we already have the post objects list. Unnecessary network calls equal unnecessary network usage, the extra load on the battery and extra space.
Technically, when the orientation changes, Activity shuts down and restarts, calling separate AsyncTask which creates another background thread on which we communicate with the internet and fetch the data.
Or consider another scenario, we open the app, the current activity A creates a background thread 1 which sends a call to the internet. While the result is coming back, the user rotates the screen. Which restarts the current activity, call it B and creates another background thread 2 which also sends a call to the internet. Now there are two threads waiting for two network calls, equivalent to the extra network usage and longer time to get results. Also, the first async task will return data to an activity no longer there. These two backgrounds threads will keep on running until they get the result, resulting in extra memory usage.
To avoid this, I use AsynTaskLoader instead of AsyncTask which prevents duplicate loading. Loaders are registered with an ID by a component LoaderManager facilitating them to live beyond the lifecycle of the activity they are associated with. AsyncTaskLoader loads the data in the background thread. When the device is rotated, the LoadingManager makes sure runningLoader connects to the onLoadFinished function. Loader loads in the background, and sends the result to the onLoadFinished function on completion.
Changes made in the MainActivity to incorporate AsyncTaskLoader –
Delete inner class DDQueryTask.
Implement LoaderManager.LoaderCallbacks<Post[]>.
Override the methods onCreateLoader, onLoadFinished and onLoaderReset and implement them to load data in the background.
Initialize the loader inside the onCreate method.
Restart the loader when we click the REFRESH button on the menu.
After this, when we rotate the MainActivity we don’t see the data being reloaded.
WebView inside the PostDetailsActivity
When we click on a post in the MainActivity, it opens PostDetailsActivity which displays the URL in the WebView. When I rotate the screen, reloading happens. To prevent this we just need to add the following code to the PostDetailsActivity’s activity tag in AndroidManifest.xml. Here we don’t need to use AsyncTaskLoader or cache because the data is already there and we need to re-render it in the landscape view.
When we use android:configChanges, the activitywon’t shut down and restart on screen rotation. Instead, the onCOnfigurationChanged method is called which handles the configuration changes automatically. When this method is called, the resources object gets updated with newresource values matching the new configuration.
Now when we rotate the PostDetailsActivity screen, the webview doesn’t reload the URL.
Files Changed:
MainActivity.java
AndroidManifest.xml
Today’s update took me 2 Pomodoros i.e. 50 minutes.
Created another activity (PostDetailActivity) to be called using intent when we click on an individual post on our post list (recyclerview). We pass the slug derived from JSON data for that post as extra data to the intent.
PostDetailActivity’s layout is a webview. The webview will fetch the post using the URL. The URL is formed using the slug passed as extra with the intent. We pass the slug as a string to the getUrl method of the NetworkUtils class.
Added another method getUrl(String slug) inside the NetworkUtils class to create URL for fetching an individual post.
Added another class (Post) to store attributes of a single post. It stores id, title, slug for an individual post.
Each item of the RecyclerView was displayed as String before. But now each item is linked to a Post object having an id, title, and slug. And we have to pass the slug when we click on an item. Therefore we pass Post object instead of String Object to PostAdapterOnClickHandler’sonClick method. Changed String array to Post array for storing the posts received from the JSON.
Similarly implementation of the onCLick method of PostAdapterOnClickHandler in the MainActivity changes. Now it starts an intent which sends a call to PostDetailActivity with a slug as extra data.
The DDQueryTask returns a Post objects array instead of String array.
We extract the post title from Postobject using the getter methods to display them in the item’s TextView in PostAdapter class’s onBindViewHolder method.
In DDJsonUtils, we extract the post id, title, and slug as store the data as Post object inside the Post array.
Files Created
Post.java [Store the attributes of a post like an id, title, slug]
PostDetailsActivity.java [Fetch the correct URL corresponding to given slug in received Intent and display it using webview.]
activity_post_details.xml [Display the webview]
Files Modified
MainActivity.java,
activity_main.xml
NetworkUtils.java
DDJsonUtils.java
DDQueryTask
AndroidManifest.xml
All the above work was done in 3 Pomodoros, ie, 1 hour 15 minutes.
You can follow the progress on this application development on Day 0,Day 1, Day 2.