--

Wednesday 26 December 2018

Chapter - 4 : Modern Android Architecture ‘Android Jetpack’ - Navigation library

      Navigation Architecture Component was introduced with Android Jetpack to facilitate the implementation of the navigation within your app and simultaneously enforce guidelines that will provide a consistent and predictable navigation to the end users.


Why it helps ?

  • Aside from one-time setup, an app should have a fixed starting screen when started from the launcher.
  • Android navigation stack should follow a LIFO structure. With the starting screen at bottom of the stack and the current on top.
  • The up button should never exit your app.
  • With the exception of the bottom element of the stack, the up button should function identically to the system back button.
  • The navigation stack should be consistent even if it’s triggered from an outside source. This is a common mistake when using deep links to app content, where the system back button and up button have distinct destinations.


Key definitions


NavHost: Context or container that has all the information it needs to handle any navigation-related actions on its own.
NavController: Component used to perform navigation between destinations.
NavigationUI: Component that connects the navigation with some material design components (such as the BottomNavigationView and the DrawerLayout)


Implement Navigation


      The Navigation Architecture Component is designed to have a single activity with multiple Fragments. The Activity hosts a Navigation Graph. If your app has multiple activities each Activity hosts its own Navigation Graph.

Gradle Dependency




Add the following dependencies to your app or module build.gradle file



Navigation Graph XML

       This component handles navigation as a graph, where each node represents a screen. These nodes are called destinations and are bounded by each other through actions. The set of destinations and actions compose the navigation graph.

       Let’s start by the creation of our navigation graph. So, on your res directory go to New > Android resource file and select Navigation from the resource type list. On the newly created navigation directory, we can start writing our navigation graph.



android:name: Attribute that flags the fragment as a NavHost — A container for the navigation graph that allows destinations to be swapped as the user navigates through the app.
app:navGraph: Connects the NavHostFragment to the navigation graph resource.
app:defaultNavHost: When set to true, NavHostFragment is capable of intercepting the system’s back button presses.
Now you should be able to hop to the design editor of your navigation graph resource and see which resource holds your NavFragmentHost.

       Now going back to handling up and back navigation, how does it happen? Since the navigation graph is the single source of truth for all navigation within the app, the navigation component does the right thing when navigating back or up. Not only does it handle this, but it correctly takes care of things such as the back stack, transactions, pop behaviour and transition animations.



Now that the layouts are ready, let’s see how to navigate from one Fragment to the other via the actions. NavController manages app navigation within the NavHost.

public class FirstFragment extends Fragment {
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.navigation_first_fragment, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
final Bundle bundle = new Bundle();
bundle.putBoolean("test_boolean", true);
final NavController navController = Navigation.findNavController(getActivity(), R.id.my_nav_host_fragment);
Button button = view.findViewById(R.id.button_frag1);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
navController.navigate(R.id.action_firstFragment_to_secondFragment, bundle);
}
});
}
}


Trigger navigation

       So we have established a connection with a destination, the next move is to actually that transition, right? Now, each NavHost has a controller — NavController — that is capable of swapping destinations with one of these two methods:
navigate(): Pushes the specified action destination to the stack.
navigateUp(): Pops to the top destination on the stack. On this case, this method would trigger an app exit since fragment A is the last on the stack.

Overall Jetpack makes android development easy and it helps you to focus more on your app’s business logic rather than spending more time on framework specific implementations.

I have uploaded the latest source code in GitHub for your reference. Kindly raise your queries in the command section.


Reference

https://developer.android.com/topic/libraries/architecture/navigation/
https://proandroiddev.com/android-jetpack-navigation-to-the-rescue-fe588271d36
https://medium.com/deemaze-software/android-jetpack-navigation-architecture-component-b603c9a8100c


Happy coding!!!
Cheers!!!

Monday 3 December 2018

Chapter -3 : Modern Android Architecture ‘Android Jetpack’ - Infinity feeds using Paging Library


       Android Paging library is a component of android jetpack which makes it easier to load data gradually in our app. The Paging library supports both large bounded list and unbounded lists, such as continuously updating feeds. For instance consider the Instagram App, it shows you a list of posts, and the app has too many posts, but it does not load all the post at once, it shows you some posts, as soon as you reaches the last item of the post list it loads more posts. This is called paging.

      This post provides an overview of how we can use the Paging Library to request and display data that users want to see while consuming system resources more economically and elegantly.

Merits of using Paging

  • We only load a small chunk from your large data set, it will consume less bandwidth.
  • The app will use less resources resulting in a smooth app and nice user experience.

Pre-Requisites


  • Before moving ahead, you should go through these tutorials first, as we are going to use these things. Using Retrofit in Android: Complete Retrofit Playlist from Scratch.
  • We will use the Retrofit Library to fetch the data from backend API. The above tutorial discusses about Retrofit and Building API from scratch. So you can check the series. 
  • RecyclerView: RecyclerView Tutorial.
  • We will load the items in a RecyclerView after fetching it from server.
  • Android ViewModel: Android ViewModel Tutorial
  • This is another component from Android Jetpack. It helps us to store UI related data in a more efficient way.

In this tutorial I am going to use a readymade News API. In the above API URL we are passing the below parameters.

page: The number of page that we want to fetch.
pagesize: The total number of items that we want in the page.

The above URL will give the following response.



The data is coming from NewsAPI, and it has a very large data set, so you will get may be an infinite number of pages.
Now our task is to fetch from the page 1 and load the next page as soon as the user reaches the end of the list. And to do this we will use the Android Paging Library. Please find folder structure of sample application,



Step 1: Add the Paging library to the app

Go to app level build.gradle file and add the following dependencies.


After adding all the above required dependencies sync your project and you are good to go. Here we have added a lot of dependencies these are:

Retrofit and Gson - For fetching and parsing JSON from URL.
ViewModel - Android architecture component for storing data.
Paging - The paging library.
RecyclerView and CardView - For building the List.
Picasso - For loading image from URL.

Step 2 : Creating Model Class

Now we will create a model class in our project. We need this class to parse the JSON response automatically. Please refer above a really complex nested JSON in the response. So we need many classes to bind the response into respective java class automatically.
Create a file named Post.java and write the following code in it.



Step 3 : Creating Retrofit Singleton Class


Each time when we want to fetch data from a new page, we need the Retrofit object. So creating a singleton instance of Retrofit is a good idea. For the singleton instance I will create a new class named RetrofitFactory.


We have to create RetrofitApi interface to fetch news feed.


Step 4 : Creating Item Data Source

Now here comes the very important thing, the data source of our item from where we will fetch the actual data. And you know that we are using the News API.

For creating a Data Source we have many options, like ItemKeyedDataSource, PageKeyedDataSource, PositionalDataSource. For this tutorial we are going to use PageKeyedDataSource, as in our API we need to pass the page number for each page that we want to fetch. So here the page number becomes the Key of our page.

In this scenario, we would be using a PageKeyedDataSource. The following code shows how we can create PageKeyedDataSource for our NewsFeedDataSource class.


public class NewsFeedDataSource extends PageKeyedDataSource<Long, Post> implements Constants {
private static final String TAG = NewsFeedDataSource.class.getSimpleName();
private InfinityFeedApp appController;
private MutableLiveData networkState;
private MutableLiveData initialLoading;

public NewsFeedDataSource(InfinityFeedApp appController) {
this.appController = appController;
networkState = new MutableLiveData();
initialLoading = new MutableLiveData();
}

public MutableLiveData getNetworkState() {
return networkState;
}

public MutableLiveData getInitialLoading() {
return initialLoading;
}

@Override
public void loadInitial(@NonNull LoadInitialParams<Long> params,
@NonNull final LoadInitialCallback<Long, Post> callback) {
initialLoading.postValue(NetworkState.LOADING);
networkState.postValue(NetworkState.LOADING);
appController.getRetrofitApi().getFeeds(QUERY, API_KEY, 1, params.requestedLoadSize)
.enqueue(new Callback<NewsFeed>() {


@Override
public void onResponse(Call<NewsFeed> call, Response<NewsFeed> response) {
if (response.isSuccessful()) {
callback.onResult(response.body().getPosts(), null, 2l);
initialLoading.postValue(NetworkState.LOADED);
networkState.postValue(NetworkState.LOADED);
} else {
initialLoading.postValue(new NetworkState(NetworkState.Status.FAILED, response.message()));
networkState.postValue(new NetworkState(NetworkState.Status.FAILED, response.message()));
}
}

@Override
public void onFailure(Call<NewsFeed> call, Throwable t) {
String errorMessage = t == null ? "unknown error" : t.getMessage();
networkState.postValue(new NetworkState(NetworkState.Status.FAILED, errorMessage));
}
});
}

@Override
public void loadBefore(@NonNull LoadParams<Long> params,
@NonNull LoadCallback<Long, Post> callback) {
}

@Override
public void loadAfter(@NonNull final LoadParams<Long> params,
@NonNull final LoadCallback<Long, Post> callback) {
Log.i(TAG, "Loading Rang " + params.key + " Count " + params.requestedLoadSize);
networkState.postValue(NetworkState.LOADING);
appController.getRetrofitApi().getFeeds(QUERY, API_KEY, params.key, params.requestedLoadSize).enqueue(new Callback<NewsFeed>() {

@Override
public void onResponse(Call<NewsFeed> call, Response<NewsFeed> response) {
if (response.isSuccessful()) {
long nextKey = (params.key == response.body().getTotalResults()) ? null : params.key + 1;
callback.onResult(response.body().getPosts(), nextKey);
networkState.postValue(NetworkState.LOADED);
} else
networkState.postValue(new NetworkState(NetworkState.Status.FAILED, response.message()));
}

@Override
public void onFailure(Call<NewsFeed> call, Throwable t) {
String errorMessage = t == null ? "unknown error" : t.getMessage();
networkState.postValue(new NetworkState(NetworkState.Status.FAILED, errorMessage));
}
});
}
}


Note: I’m using LiveData to push the network updates to the UI. This is because it’s lifecycle aware and will handle the subscription for us.
  • We extended PageKeyedDataSource<Integer, Item> in the above class. Integer here defines the page key. Every time we want a new page from the API we need to pass the page number that we want which is an integer. Item is the Post model class that we will get from the API or that we want to get. 
  • Then we defined the size of a page which is 21, the initial page number which is 1. You are free to change these values if you want.
Then we have 3 overridden methods.
  • loadInitials(): This method will load the initial data. Or you can say it will be called once to load the initial data, or first page according to this post.
  • loadBefore(): This method will load the previous page.
  • loadAfter(): This method will load the next page.

Step 5 : Creating Item Data Source Factory


DataSourceFactory is responsible for retrieving the data using the DataSource and PagedList configuration. We are going to use MutableLiveData<> to store our PageKeyedDataSource and for this we have to create NewsFeedDataFactory class by extending DataSource.Factory.


Step 6 : Setup the ViewModel


The view model will be responsible for creating the PagedList along with its configurations and send it to the activity so it can observe the data changes and pass it to the adapter.

PagedList is a wrapper list that holds your data items (in our case the list of news we need to display) and invokes the DataSource to load the elements. It typically consists of a background executor (which fetches the data) and the foreground executor (which updates the UI with the data).

For instance, let’s say we have some data that we add to the DataSource in the background thread. The DataSource invalidates the PagedList and updates its value. Then on the main thread, the PagedList notifies its observers of the new value. Now the PagedListAdapter knows about the new value.

PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(10)
.setPrefetchDistance(10)
.setPageSize(20).build();


  1. setEnablePlaceholders(boolean enablePlaceholders) - Enabling placeholders mean we can see placeholders instead of the image since it is not fully loaded. 
  2. setInitialLoadSizeHint(int initialLoadSizeHint) - The number of items to load initially. 
  3. setPageSize(int pageSize) - The number of items to load in the PagedList. 
  4. setPrefetchDistance(int prefetchDistance) — The number of preloads that occur. For instance, if we set this to 10, it will fetch the first 10 pages initially when the screen loads.
Now create a class named ItemViewModel and write the following code.

public class NewsFeedViewModel extends ViewModel {
private Executor executor;
private LiveData<NetworkState> networkState;
private LiveData<PagedList<Post>> feedsLiveData;
private InfinityFeedApp infinityFeedApp;

public NewsFeedViewModel(@NonNull InfinityFeedApp infinityFeedApp) {
this.infinityFeedApp = infinityFeedApp;
init();
}

private void init() {
executor = Executors.newFixedThreadPool(5);
NewsFeedDataFactory newsFeedDataFactory = new NewsFeedDataFactory(infinityFeedApp);
networkState = Transformations.switchMap(newsFeedDataFactory.getFeedDataSourceMutableLiveData(),
dataSource -> dataSource.getNetworkState());

PagedList.Config pagedListConfig =
(new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(10)
.setPrefetchDistance(10)
.setPageSize(20).build();

feedsLiveData = (new LivePagedListBuilder(newsFeedDataFactory, pagedListConfig))
.setFetchExecutor(executor)
.build();
}

public LiveData<NetworkState> getNetworkState() {
return networkState;
}

public LiveData<PagedList<Post>> getFeedsLiveData() {
return feedsLiveData;
}
}


Step 7 : Creating the PagedListAdapter

PagedListAdapter is an implementation of RecyclerView.Adapter that presents data from a PagedList. It uses DiffUtil as a parameter to calculate data differences and do all the updates for you.

public static DiffUtil.ItemCallback<Post> DIFF_CALLBACK = new DiffUtil.ItemCallback<Post>() {
@Override
public boolean areItemsTheSame(@NonNull Post oldItem, @NonNull Post newItem) {
return oldItem.id == newItem.id;
}
@Override
public boolean areContentsTheSame(@NonNull Post oldItem, @NonNull Post newItem) {
return oldItem.equals(newItem);
}
};

FeedListAdapter class extends PageListAdapter and does not override the usual getItemCount() as this is provided by the PageList object. If we do need to override this method, we need to add super.getItemCount() to the method. 


public class FeedListAdapter extends PagedListAdapter<Post, RecyclerView.ViewHolder> {
private static final int TYPE_PROGRESS = 0;
private static final int TYPE_ITEM = 1;

private Context context;
private NetworkState networkState;

public FeedListAdapter(Context context) {
super(Post.DIFF_CALLBACK);
this.context = context;
}

@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
if(viewType == TYPE_PROGRESS) {
NetworkItemBinding headerBinding = NetworkItemBinding.inflate(layoutInflater, parent, false);
NetworkStateItemViewHolder viewHolder = new NetworkStateItemViewHolder(headerBinding);
return viewHolder;
} else {
FeedItemBinding itemBinding = FeedItemBinding.inflate(layoutInflater, parent, false);
ArticleItemViewHolder viewHolder = new ArticleItemViewHolder(itemBinding);
return viewHolder;
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if(holder instanceof ArticleItemViewHolder) {
((ArticleItemViewHolder)holder).bindTo(getItem(position));
} else {
((NetworkStateItemViewHolder) holder).bindView(networkState);
}
}

private boolean hasExtraRow() {
if (networkState != null && networkState != NetworkState.LOADED) {
return true;
} else {
return false;
}
}

@Override
public int getItemViewType(int position) {
if (hasExtraRow() && position == getItemCount() - 1) {
return TYPE_PROGRESS;
} else {
return TYPE_ITEM;
}
}

public void setNetworkState(NetworkState newNetworkState) {
NetworkState previousState = this.networkState;
boolean previousExtraRow = hasExtraRow();
this.networkState = newNetworkState;
boolean newExtraRow = hasExtraRow();
if (previousExtraRow != newExtraRow) {
if (previousExtraRow) {
notifyItemRemoved(getItemCount());
} else {
notifyItemInserted(getItemCount());
}
} else if (newExtraRow && previousState != newNetworkState) {
notifyItemChanged(getItemCount() - 1);
}
}

public class ArticleItemViewHolder extends RecyclerView.ViewHolder {
private FeedItemBinding binding;
public ArticleItemViewHolder(FeedItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}

public void bindTo(Post post) {
binding.itemImage.setVisibility(View.VISIBLE);
binding.itemDesc.setVisibility(View.VISIBLE);
String author = post.getAuthor() == null || post.getAuthor().isEmpty() ? context.getString(R.string.author_name) : post.getAuthor();
String titleString = String.format(context.getString(R.string.item_title), author, post.getTitle());
SpannableString spannableString = new SpannableString(titleString);
spannableString.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context.getApplicationContext(), R.color.secondary_text)),
titleString.lastIndexOf(author) + author.length() + 1, titleString.lastIndexOf(post.getTitle()) - 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
binding.itemTitle.setText(spannableString);
binding.itemTime.setText(String.format(context.getString(R.string.item_date), Utility.getDate(post.getPublishedAt()), Utility.getTime(post.getPublishedAt()))); binding.itemDesc.setText(post.getDescription());
Picasso.get().load(post.getUrlToImage()).resize(250, 200).into(binding.itemImage);
}
}

public class NetworkStateItemViewHolder extends RecyclerView.ViewHolder {
private NetworkItemBinding binding;
public NetworkStateItemViewHolder(NetworkItemBinding binding) {
super(binding.getRoot());
this.binding = binding;
}

public void bindView(NetworkState networkState) {
if (networkState != null && networkState.getStatus() == NetworkState.Status.RUNNING) {
binding.progressBar.setVisibility(View.VISIBLE);
} else {
binding.progressBar.setVisibility(View.GONE); 
}


if (networkState != null && networkState.getStatus() == NetworkState.Status.FAILED) {
binding.errorMsg.setVisibility(View.VISIBLE);
binding.errorMsg.setText(networkState.getMsg());
} else {
binding.errorMsg.setVisibility(View.GONE);
}
}
}
}


Step 8 : Displaying the Paged List at Activity

The last step is to set up our Activity class with the ViewModel, RecyclerView, PagedListAdapter:





Now you are done and you can try running your application. If everything is fine you will see the below output.
I have uploaded the latest source code in GitHub for your reference. Kindly raise your queries in the command section.


References :

Paging library overview
https://developer.android.com/topic/libraries/architecture/paging/

Paging library UI components and considerations
https://developer.android.com/topic/libraries/architecture/paging/ui

Paging library data components and considerations
https://developer.android.com/topic/libraries/architecture/paging/data

https://proandroiddev.com/8-steps-to-implement-paging-library-in-android-d02500f7fffe
https://www.simplifiedcoding.net/android-paging-library-tutorial/



Happy coding!!!
Cheers!!!