Android Knowledge

The News App – Prerequisites.

Project Overview

We will be starting with a new series where I’ll create the news app, the project is quite big so it will take around six videos to complete it, okay?

Also, we will be covering five major advanced topics such as MVVM, Retrofit, Navigation Components, Coroutines, and Room Databases in one single project. So, I highly recommend doing a revision of each topic before starting with the project.

Topic Videos:

  1. MVVM:
    https://youtu.be/DTE1dbdluh4
  2. ROOM:
    https://youtu.be/zWbryaoVBuk
  3. Navigation Component:
    https://youtu.be/yWWuOqFRwfg
  4. Coroutines:
    https://youtu.be/onnnefZCgmQ
  5. Retrofit:
    https://youtu.be/KJSBsRKqNwU

Project Demo

Alright, let’s have a look at the demo.

This is how it looks!

I have kept the project very minimal and useful.

headlines ss

We have a bottom navigation with three categories – headlines, favorites, and search.

As you can see in the headlines the latest news is shown here. All, of them, are fetched from news API in real-time.

You, can even click on it and see it opens up the article in a web view, great right?

article ss

You can scroll through it and you can save it in your favorites section by clicking on this floating acting button.

Look, it’s saved here in favorites – not, just that but you can also remove it from favorites by swiping it and you can undo it as well, right?

favourite ss

Then, go to the search section.

Here, you can literally search anything it will come up with the results – see, there it is!

search ss

All, of these, might look very easy to you and it is indeed very easy…it’s, just that we have to write a lot of code for them.

Technical Talks about Project

Now, let’s go into the technical side of it.

To, display all these articles are using news API which I’ll show you later. So, with the help of that, it fetches the real-time data or news and displays it on the recycler view.

Then, with the help of web view – we, can open the article in the app itself without going to any browser.

Then, by clicking on add to fav – that, particular article gets saved in the room database so what you see here is all saved in the room database hence later whenever you open the app again – they, will be still here.

Lastly, search uses API only, and other topics like MVVM will help us to make the project organized and segregated in model view and ViewModel not, just that but also the navigation component will help us to navigate from one screen to another screen effortlessly.

Source Code

Note: This is my practice project which works the same way as the original one but please copy only the required code because copying the entire code of gradle or manifest will lead to errors related to the project package name.

colors.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="black">#FF000000</color>
    <color name="white">#FFFFFFFF</color>
    <color name="purple">#593c8f</color>
    <color name="yellow">#FFC000</color>
</resources>

themes.xml

<resources xmlns:tools="http://schemas.android.com/tools">
    <!-- Base application theme. -->
    <style name="Base.Theme.NewsProjectPractice" parent="Theme.Material3.DayNight.NoActionBar">
        <!-- Customize your light theme here. -->
         <item name="colorPrimary">@color/purple</item>
         <item name="colorPrimaryVariant">@color/purple</item>
         <item name="android:statusBarColor">@color/purple</item>
    </style>

    <style name="Theme.NewsProjectPractice" parent="Base.Theme.NewsProjectPractice" />

    <style name="App.Custom.Indicator" parent="Widget.Material3.BottomNavigationView.ActiveIndicator">
        <item name="android:color">@color/yellow</item>
    </style>
</resources>

Drawables:

  1. Search for the language icon and add it as a headlines icon.
  2. Search for the favorites icon as the heart.
  3. Search for the search icon.

search_border.xml

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

    <stroke
        android:color="@color/purple"
        android:width="2dp"/>
    <corners
        android:radius="14dp"/>

</shape>

build gradle (Project)

// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
    repositories {
        google()
    }
    dependencies {
        val navVersion = "2.7.5"
        classpath("androidx.navigation:navigation-safe-args-gradle-plugin:$navVersion")
    }
}

plugins {
    id("com.android.application") version "8.1.1" apply false
    id("org.jetbrains.kotlin.android") version "1.9.0" apply false
    id("com.google.devtools.ksp") version "1.9.0-1.0.13" apply false
}

build gradle (Module)

plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
    id("com.google.devtools.ksp")
    id("androidx.navigation.safeargs.kotlin")
}

android {
    namespace = "com.example.newsprojectpractice"
    compileSdk = 34

    defaultConfig {
        applicationId = "com.example.newsprojectpractice"
        minSdk = 28
        targetSdk = 33
        versionCode = 1
        versionName = "1.0"

        testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            isMinifyEnabled = false
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_1_8
        targetCompatibility = JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = "1.8"
    }
    buildFeatures{
        viewBinding = true
    }
}

dependencies {

    implementation("androidx.core:core-ktx:1.12.0")
    implementation("androidx.appcompat:appcompat:1.6.1")
    implementation("com.google.android.material:material:1.10.0")
    implementation("androidx.constraintlayout:constraintlayout:2.1.4")
    testImplementation("junit:junit:4.13.2")
    androidTestImplementation("androidx.test.ext:junit:1.1.5")
    androidTestImplementation("androidx.test.espresso:espresso-core:3.5.1")

    // Architectural Components
    implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")

    // Room
    implementation ("androidx.room:room-runtime:2.6.0")
    ksp ("androidx.room:room-compiler:2.6.0")

    // Kotlin Extensions and Coroutines support for Room
    implementation ("androidx.room:room-ktx:2.6.0")

    // Coroutines
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.1")
    implementation ("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.1")

    // Coroutine Lifecycle Scopes
    implementation ("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.2")
    implementation ("androidx.lifecycle:lifecycle-runtime-ktx:2.6.2")

    // Retrofit
    implementation ("com.squareup.retrofit2:retrofit:2.9.0")
    implementation ("com.squareup.retrofit2:converter-gson:2.9.0")
    implementation ("com.squareup.okhttp3:logging-interceptor:4.5.0")

    // Navigation Components
    implementation ("androidx.navigation:navigation-fragment-ktx:2.7.5")
    implementation ("androidx.navigation:navigation-ui-ktx:2.7.5")

    // Glide
    implementation ("com.github.bumptech.glide:glide:4.12.0")
    ksp ("com.github.bumptech.glide:compiler:4.12.0")
}

bottom_navigation_menu.xml

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

    <item android:id="@+id/headlinesFragment"
        android:title="Headlines"
        android:icon="@drawable/baseline_headlines_24" />

    <item android:id="@+id/favouritesFragment"
        android:title="Favourites"
        android:icon="@drawable/baseline_favorite_24" />

    <item android:id="@+id/searchFragment"
        android:title="Search"
        android:icon="@drawable/baseline_search_24" />

</menu>

Packages and Fragments

AndroidManifest.xml

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

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.NewsProjectPractice"
        tools:targetApi="31">
        <activity
            android:name=".ui.NewsActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <meta-data
            android:name="preloaded_fonts"
            android:resource="@array/preloaded_fonts" />
    </application>

</manifest>

Fragments XML Layouts

fragment_article.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent">

    <WebView
        android:id="@+id/webView"
        android:layout_width="match_parent"
        android:layout_height="match_parent" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id="@+id/fab"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/baseline_favorite_24"
        app:fabSize="normal"
        android:layout_marginBottom="32dp"
        android:layout_marginEnd="32dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

fragment_favorites.xml

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

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Favourites."
        android:textSize="30sp"
        android:fontFamily="@font/poppins"
        android:textColor="@color/purple"
        android:textStyle="bold"
        android:layout_marginStart="8dp"
        android:layout_marginTop="12dp"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerFavourites"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_marginTop="60dp"
        android:layout_marginStart="8dp"/>

</FrameLayout>

item_error.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView
    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="wrap_content"
    android:orientation="horizontal"
    app:cardCornerRadius="10dp">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:id="@+id/errorText"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:layout_weight="2"
            android:textSize="16sp"
            android:gravity="center_horizontal"
            android:text="No Internet"
            android:textAlignment="center" />

        <Button
            android:id="@+id/retryButton"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_margin="16dp"
            android:layout_weight="1"
            android:text="Retry" />
    </LinearLayout>

</androidx.cardview.widget.CardView>

fragment_headlines.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Headlines."
        android:textSize="30sp"
        android:fontFamily="@font/poppins"
        android:textColor="@color/purple"
        android:textStyle="bold"
        android:layout_marginStart="8dp"
        android:layout_marginTop="12dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerHeadlines"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:clipToPadding="false"
        android:paddingBottom="50dp"
        android:layout_marginTop="70dp"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <include
        android:id="@+id/itemHeadlinesError"
        layout="@layout/item_error"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/paginationProgressBar"
        style="?attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="invisible"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

fragment_search.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Search."
        android:textSize="30sp"
        android:fontFamily="@font/poppins"
        android:textColor="@color/purple"
        android:textStyle="bold"
        android:layout_marginStart="8dp"
        android:layout_marginTop="12dp"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

    <EditText
        android:id="@+id/searchEdit"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Search news..."
        android:layout_marginTop="70dp"
        android:layout_marginEnd="8dp"
        android:layout_marginStart="8dp"
        android:padding="8dp"
        android:textSize="14sp"
        android:fontFamily="@font/poppins"
        android:drawableTint="@color/purple"
        android:background="@drawable/search_border"
        android:drawableEnd="@drawable/baseline_search_24"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/recyclerSearch"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:paddingBottom="50dp"
        android:clipToPadding="false"
        android:layout_marginStart="8dp"
        android:layout_marginEnd="8dp"
        android:layout_marginTop="12dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/searchEdit"
        tools:layout_editor_absoluteX="0dp" />

    <include
        android:id="@+id/itemSearchError"
        layout="@layout/item_error"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_margin="16dp"
        android:visibility="invisible"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

    <ProgressBar
        android:id="@+id/paginationProgressBar"
        style="?attr/progressBarStyle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="invisible"
        android:background="@android:color/transparent"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

item_news.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.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="wrap_content"
    android:padding="8dp">

        <ImageView
            android:id="@+id/articleImage"
            android:layout_width="160dp"
            android:layout_height="90dp"
            android:scaleType="centerCrop"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/articleSource"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="4dp"
            android:text="SOURCE"
            android:fontFamily="@font/poppins"
            android:textColor="@color/purple"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/articleImage" />

        <TextView
            android:id="@+id/articleTitle"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:ellipsize="end"
            android:maxLines="2"
            android:text="TITLE"
            android:textColor="@color/black"
            android:textSize="15sp"
            android:textStyle="bold"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintLeft_toRightOf="@+id/articleImage"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/articleDescription"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginStart="8dp"
            android:layout_marginTop="4dp"
            android:ellipsize="end"
            android:maxLines="2"
            android:text="DESCRIPTION"
            android:textColor="@color/black"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/articleImage"
            app:layout_constraintTop_toBottomOf="@+id/articleTitle" />

        <TextView
            android:id="@+id/articleDateTime"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="12dp"
            android:text="DATE TIME"
            android:textSize="11sp"
            android:layout_marginStart="8dp"
            android:textColor="@color/black"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/articleDescription" />

</androidx.constraintlayout.widget.ConstraintLayout>

activity_news.xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
    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:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".ui.NewsActivity">

    <FrameLayout
        android:id="@+id/flFragment"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintHorizontal_bias="0.5"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <androidx.fragment.app.FragmentContainerView
            android:id="@+id/newsNavHostFragment"
            android:name="androidx.navigation.fragment.NavHostFragment"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:defaultNavHost="true"
            app:navGraph="@navigation/news_nav_graph" />

    </FrameLayout>

    <com.google.android.material.bottomnavigation.BottomNavigationView
        android:id="@+id/bottomNavigationView"
        android:layout_width="match_parent"
        android:layout_height="80dp"
        app:menu="@menu/bottom_navigation_menu"
        app:itemTextColor="@color/white"
        app:itemIconTint="@color/white"
        android:background="@color/purple"
        app:itemActiveIndicatorStyle="@style/App.Custom.Indicator"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

news_nav_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/news_nav_graph"
    app:startDestination="@id/headlinesFragment">

    <fragment
        android:id="@+id/headlinesFragment"
        android:name="com.example.newsprojectpractice.ui.fragments.HeadlinesFragment"
        android:label="HeadlinesFragment">
        <action
            android:id="@+id/action_headlinesFragment_to_articleFragment"
            app:destination="@id/articleFragment"/>

    </fragment>
    <fragment
        android:id="@+id/favouritesFragment"
        android:name="com.example.newsprojectpractice.ui.fragments.FavouritesFragment"
        android:label="FavouritesFragment">
        <action
            android:id="@+id/action_favouritesFragment_to_articleFragment"
            app:destination="@id/articleFragment"/>

    </fragment>
    <fragment
        android:id="@+id/searchFragment"
        android:name="com.example.newsprojectpractice.ui.fragments.SearchFragment"
        android:label="SearchFragment">
        <action
            android:id="@+id/action_searchFragment_to_articleFragment"
            app:destination="@id/articleFragment"/>

    </fragment>
    <fragment
        android:id="@+id/articleFragment"
        android:name="com.example.newsprojectpractice.ui.fragments.ArticleFragment"
        android:label="ArticleFragment" >
        <argument
            android:name="article"
            app:argType="com.example.newsprojectpractice.models.Article" />
    </fragment>
</navigation>

API Key

Alright, now the only last thing that is left is the API key.

Where, do we get this API key from?

So, every API has its own key which is private and you are not supposed to share it with anyone, and with the help of that key, you can access the data.

As we want news data there are varieties of news APIs available on the internet – out, of which we will be using News API.

News API: https://newsapi.org/

It’s, free, easy to integrate, and widely used.

Just, to show you – we, also have another famous news API called g news – if, you want you can use it too but as a beginner follow me.

gNews:

So, the best part about news API is that it provides a lot of customization like country, language, publishers, or timing – you will know when we will implement retrofit.

Now, let’s focus on the API key.

As a – new user clicks on the get API key.

Fill, in all the details like name, email, and password and submit it…then, at the end, it will provide us with an api key…and, that’s it.

Conclusion

All, our prerequisites are done!

Now, in the upcoming videos – we can write the code without any interference…and, that’s where the real logic begins…so, stay tuned for that, okay!

You can create a splash screen if you want to – click here for the code

Please subscribe to my YouTube channel:

Android Knowledge – Click here