How to Build a Stock Market News Android App using Retrofit?
Building a stock market news app can be a great way to stay informed about the latest developments in the financial world. This article will explore how to build a simple news app using Kotlin and Retrofit. A sample video is given below to get an idea about what we are going to do in this article.
Step-by-Step Implementation:
Step 1: First, let’s start by setting up a new project in Android Studio. Make sure to select the “Empty Activity” template and choose Kotlin as the language. Once the project is set up, we can start adding the necessary dependencies to our app.
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
Step 2: In this example, we are using the GET method to retrieve the top headlines from a news API. We are passing in the country and API key as query parameters.
Kotlin
interface FinanceApi { @GET ( "news/all" ) fun getAllDetails( @Query ( "exchanges" ) exchanges: String , @Query ( "api_token" ) api_token : String) : Call<News> } |
Java
public interface FinanceApi { @GET ( "news/all" ) Call<News> getAllDetails( @Query ( "exchanges" ) String exchanges, @Query ( "api_token" ) String api_token); } |
Step 3: Now that we have defined our endpoint, we can use Retrofit to create an instance of our NewsAPI interface.
Kotlin
object RetrofitInstance { val api : FinanceApi by lazy { Retrofit.Builder() .baseUrl( "https://api.marketaux.com/v1/" ) .addConverterFactory(GsonConverterFactory.create()) .build() .create(FinanceApi:: class .java) } } |
Java
public class RetrofitInstance { private static FinanceApi apiInstance; public static synchronized FinanceApi getApi() { if (apiInstance == null ) { Retrofit retrofit = new Retrofit.Builder() .baseUrl( "https://api.marketaux.com/v1/" ) .addConverterFactory(GsonConverterFactory.create()) .build(); apiInstance = retrofit.create(FinanceApi. class ); } return apiInstance; } } |
Step 4: Generate Data Classes using the json-kotlin converter plugin
Kotlin
data class News( val `data`: List<Data>, val meta: Meta ) data class Data( val description: String, val entities: List<Entity>, val image_url: String, val keywords: String, val language: String, val published_at: String, val relevance_score: Any, val similar: List<Similar>, val snippet: String, val source: String, val title: String, val url: String, val uuid: String ) |
Java
import java.util.List; public class News { private List<Data> data; private Meta meta; public List<Data> getData() { return data; } public void setData(List<Data> data) { this .data = data; } public Meta getMeta() { return meta; } public void setMeta(Meta meta) { this .meta = meta; } } public class Data { private String description; private List<Entity> entities; private String image_url; private String keywords; private String language; private String published_at; private Object relevance_score; private List<Similar> similar; private String snippet; private String source; private String title; private String url; private String uuid; public String getDescription() { return description; } public void setDescription(String description) { this .description = description; } public List<Entity> getEntities() { return entities; } public void setEntities(List<Entity> entities) { this .entities = entities; } public String getImage_url() { return image_url; } public void setImage_url(String image_url) { this .image_url = image_url; } public String getKeywords() { return keywords; } public void setKeywords(String keywords) { this .keywords = keywords; } public String getLanguage() { return language; } public void setLanguage(String language) { this .language = language; } public String getPublished_at() { return published_at; } public void setPublished_at(String published_at) { this .published_at = published_at; } public Object getRelevance_score() { return relevance_score; } public void setRelevance_score(Object relevance_score) { this .relevance_score = relevance_score; } public List<Similar> getSimilar() { return similar; } public void setSimilar(List<Similar> similar) { this .similar = similar; } public String getSnippet() { return snippet; } public void setSnippet(String snippet) { this .snippet = snippet; } public String getSource() { return source; } public void setSource(String source) { this .source = source; } public String getTitle() { return title; } public void setTitle(String title) { this .title = title; } public String getUrl() { return url; } public void setUrl(String url) { this .url = url; } public String getUuid() { return uuid; } public void setUuid(String uuid) { this .uuid = uuid; } } public class Entity { // Entity fields and methods } public class Meta { // Meta fields and methods } public class Similar { // Similar fields and methods } |
Design UI using XML
Step 5: activity_main.xml file
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 = ".MainActivity" > < androidx.recyclerview.widget.RecyclerView android:id = "@+id/recyclerView" android:layout_width = "match_parent" android:layout_height = "match_parent" app:layout_constraintBottom_toBottomOf = "parent" app:layout_constraintTop_toTopOf = "parent" tools:layout_editor_absoluteX = "1dp" /> </ androidx.constraintlayout.widget.ConstraintLayout > |
news_layout.xml
XML
<? xml version = "1.0" encoding = "utf-8" ?> < androidx.cardview.widget.CardView xmlns:android = "http://schemas.android.com/apk/res/android" xmlns:tools = "http://schemas.android.com/tools" android:layout_width = "match_parent" android:layout_height = "300dp" xmlns:app = "http://schemas.android.com/apk/res-auto" android:layout_margin = "12dp" android:elevation = "2dp" app:cardCornerRadius = "15dp" > < androidx.constraintlayout.widget.ConstraintLayout android:layout_width = "match_parent" android:layout_height = "wrap_content" > < ImageView android:id = "@+id/imageView_news" android:layout_width = "wrap_content" android:layout_height = "180dp" android:scaleType = "centerCrop" app:layout_constraintTop_toTopOf = "parent" app:layout_constraintEnd_toEndOf = "parent" app:layout_constraintStart_toStartOf = "parent" tools:srcCompat = "@tools:sample/backgrounds/scenic" /> < TextView android:id = "@+id/textView_title" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginStart = "16dp" android:layout_marginLeft = "16dp" android:text = "Sachetitistion of cigaretts?" android:textSize = "20dp" android:textColor = "@color/black" android:textStyle = "bold" android:layout_marginTop = "5dp" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintTop_toBottomOf = "@id/imageView_news" /> < TextView android:id = "@+id/textView_description" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:layout_marginStart = "16dp" android:layout_marginLeft = "16dp" android:text = "An explainer on how sachet cigarettes incentivize smoking" android:textSize = "20dp" android:layout_marginTop = "8dp" android:textColor = "@color/black" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintTop_toBottomOf = "@id/textView_title" /> </ androidx.constraintlayout.widget.ConstraintLayout > </ androidx.cardview.widget.CardView > |
activity_view_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 = ".ViewNews" > < WebView android:id = "@+id/webView" android:layout_width = "match_parent" android:layout_height = "match_parent" app:layout_constraintBottom_toBottomOf = "parent" app:layout_constraintEnd_toEndOf = "parent" app:layout_constraintStart_toStartOf = "parent" app:layout_constraintTop_toTopOf = "parent" /> </ androidx.constraintlayout.widget.ConstraintLayout > |
Step 6: Create an Adapter class For RecyclerView. In this, we will be setting up an on click listener so that we can open news URL in a new activity.
Finance Adapter
Kotlin
package com.sangyan.financeapp import android.content.Context import android.content.Intent import android.net.Uri import android.view.LayoutInflater import android.view.ViewGroup import androidx.recyclerview.widget.RecyclerView import com.bumptech.glide.Glide import com.sangyan.financeapp.databinding.NewsLayoutBinding class FinanceAdapter : RecyclerView.Adapter<FinanceAdapter.ViewHolder>() { // A list of Data objects that will // be displayed in the RecyclerView var list = ArrayList<Data>() // A function to update the list of Data objects fun setData(list : List<Data>){ // Shuffling the list of Data objects list.shuffled() // Assigning the new list to the class variable this .list = list as ArrayList<Data> // Notifying the adapter that // the data has been updated notifyDataSetChanged() } // The ViewHolder class holds the view of // a single item in the RecyclerView class ViewHolder(val binding : NewsLayoutBinding) : RecyclerView.ViewHolder(binding.root){} // The onCreateViewHolder function inflates the layout // of the item view and returns a ViewHolder object override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { // Inflating the layout of the item view using the NewsLayoutBinding val binding = NewsLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false ) // Returning a ViewHolder object // with the inflated layout return ViewHolder(binding) } // The onBindViewHolder function binds the Data object // at a particular position to the view in the ViewHolder override fun onBindViewHolder(holder: ViewHolder, position: Int) { // Getting the Data object at the current position val data = list[position] // Setting the title of the news article holder.binding.newsTitle.text = data.title // Setting the description of the news article holder.binding.newsDescription.text = data.description // Setting the source of the news article holder.binding.newsSource.text = data.source.name // Setting the time when the news article was published holder.binding.newsTime.text = data.publishedAt // Loading the image of the news article using Glide library Glide.with(holder.itemView) .load(data.urlToImage) .into(holder.binding.newsImage) // Setting an onClickListener on the item view of the RecyclerView holder.itemView.setOnClickListener { // Creating an intent to open the URL // of the news article in a browser val intent = Intent(Intent.ACTION_VIEW) intent.data = Uri.parse(data.url) // Starting the activity to open the URL in a browser holder.itemView.context.startActivity(intent) } } // The getItemCount function returns the // number of items in the RecyclerView override fun getItemCount(): Int { return list.size } } |
Java
package com.sangyan.financeapp; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.sangyan.financeapp.databinding.NewsLayoutBinding; import java.util.ArrayList; import java.util.List; public class FinanceAdapter extends RecyclerView .Adapter<FinanceAdapter.ViewHolder> { // A list of Data objects that will // be displayed in the RecyclerView private ArrayList<Data> list = new ArrayList<>(); // A function to update the list of Data objects public void setData(List<Data> list) { // Shuffling the list of Data objects java.util.Collections.shuffle(list); // Assigning the new list to the class variable this .list = new ArrayList<>(list); // Notifying the adapter that // the data has been updated notifyDataSetChanged(); } // The ViewHolder class holds the view of // a single item in the RecyclerView public static class ViewHolder extends RecyclerView.ViewHolder { NewsLayoutBinding binding; public ViewHolder(NewsLayoutBinding binding) { super (binding.getRoot()); this .binding = binding; } } // The onCreateViewHolder function inflates the layout // of the item view and returns a ViewHolder object @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { // Inflating the layout of the item view using the // NewsLayoutBinding NewsLayoutBinding binding = NewsLayoutBinding.inflate( LayoutInflater.from(parent.getContext()), parent, false ); // Returning a ViewHolder object // with the inflated layout return new ViewHolder(binding); } // The onBindViewHolder function binds the Data object // at a particular position to the view in the // ViewHolder @Override public void onBindViewHolder(ViewHolder holder, int position) { // Getting the Data object at the current position Data data = list.get(position); // Setting the title of the news article holder.binding.newsTitle.setText(data.getTitle()); // Setting the description of the news article holder.binding.newsDescription.setText( data.getDescription()); // Setting the source of the news article holder.binding.newsSource.setText( data.getSource().getName()); // Setting the time when the news article was // published holder.binding.newsTime.setText( data.getPublishedAt()); // Loading the image of the news article using Glide // library Glide.with(holder.itemView) .load(data.getUrlToImage()) .into(holder.binding.newsImage); // Setting an onClickListener on the item view of // the RecyclerView holder.itemView.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { // Creating an intent to open the URL // of the news article in a browser Intent intent = new Intent(Intent.ACTION_VIEW); intent.setData( Uri.parse(data.getUrl())); // Starting the activity to open the URL // in a browser holder.itemView.getContext() .startActivity(intent); } }); } // The getItemCount function returns the // number of items in the RecyclerView @Override public int getItemCount() { return list.size(); } } |
Step 7: Write code for the main activity which represents the UI. In MainActivity we will be creating an intent to pass data in the new ViewNews activity
Kotlin
package com.sangyan.financeapp import android.content.Intent import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import android.util.Log import androidx.recyclerview.widget.LinearLayoutManager import com.sangyan.financeapp.databinding.ActivityMainBinding import retrofit2.Call import retrofit2.Callback import retrofit2.Response class MainActivity : AppCompatActivity() { // Initializing variables private lateinit var binding : ActivityMainBinding private lateinit var myAdapter : FinanceAdapter // A companion object that holds a // constant for the URL of the news article companion object { const val URL = "News URL" } // The onCreate function is called when the activity is created override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) // Inflating the layout of the activity // using the ActivityMainBinding binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) // Initializing the adapter // for the RecyclerView myAdapter = FinanceAdapter() // Setting the layout manager and adapter for the RecyclerView binding.recyclerView.layoutManager = LinearLayoutManager(applicationContext) binding.recyclerView.adapter = myAdapter // Setting a click listener on the items in the RecyclerView myAdapter.setNewsItemClickListener(object : FinanceAdapter.SetNewsItemClickListener{ override fun setNewsItemClickListener(data: Data) { // Creating an intent to open the URL of // the news article in a new activity val intent = Intent(applicationContext,ViewNews:: class .java) intent.putExtra(URL,data.url) // Starting the new activity startActivity(intent) } }) // Making a network request to get the news data RetrofitInstance.api.getAllDetails( "BSE" , "ixxnnaiVNGyR2StAS2Ny51vRubP7VMlONNJ7ZSwl" ).enqueue(object : Callback<News>{ override fun onResponse(call: Call<News>, response: Response<News>) { // Checking if the response body is not null if (response.body()!= null ){ // Updating the data in the RecyclerView adapter myAdapter.setData(response.body()!!.data) // Logging the entities of the news data for (i in response.body()!!.data){ Log.d( "TAG" , i.entities.toString()) } } else { // Returning if the response body is null return } } // Handling the case when the network request fails override fun onFailure(call: Call<News>, t: Throwable) { Log.d( "TAG" ,t.message.toString() ) } }) } } |
Java
package com.sangyan.financeapp; import android.content.Intent; import android.os.Bundle; import android.util.Log; import androidx.appcompat.app.AppCompatActivity; import androidx.recyclerview.widget.LinearLayoutManager; import com.sangyan.financeapp.databinding.ActivityMainBinding; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; public class MainActivity extends AppCompatActivity { // Initializing variables private ActivityMainBinding binding; private FinanceAdapter myAdapter; // A companion object that holds a // constant for the URL of the news article public static final String URL = "News URL" ; // The onCreate function is called when the activity is // created @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // Inflating the layout of the activity // using the ActivityMainBinding binding = ActivityMainBinding.inflate( getLayoutInflater()); setContentView(binding.getRoot()); // Initializing the adapter // for the RecyclerView myAdapter = new FinanceAdapter(); // Setting the layout manager and adapter for the // RecyclerView binding.recyclerView.setLayoutManager( new LinearLayoutManager( getApplicationContext())); binding.recyclerView.setAdapter(myAdapter); // Setting a click listener on the items in the // RecyclerView myAdapter.setNewsItemClickListener( new FinanceAdapter.SetNewsItemClickListener() { @Override public void setNewsItemClickListener( Data data) { // Creating an intent to open the URL of // the news article in a new activity Intent intent = new Intent( getApplicationContext(), ViewNews. class ); intent.putExtra(URL, data.getUrl()); // Starting the new activity startActivity(intent); } }); // Making a network request to get the news data RetrofitInstance.getApi() .getAllDetails( "BSE" , "ixxnnaiVNGyR2StAS2Ny51vRubP7VMlONNJ7ZSwl" ) .enqueue( new Callback<News>() { @Override public void onResponse( Call<News> call, Response<News> response) { // Checking if the response body is not // null if (response.body() != null ) { // Updating the data in the // RecyclerView adapter myAdapter.setData( response.body().getData()); // Logging the entities of the news // data for (Data i : response.body().getData()) { Log.d( "TAG" , i.getEntities().toString()); } } else { // Returning if the response body is // null return ; } } // Handling the case when the network // request fails @Override public void onFailure(Call<News> call, Throwable t) { Log.d( "TAG" , t.getMessage().toString()); } }); } } |
Step 8: Write code for ViewNews activity in which we will display news in WebView.
Kotlin
package com.sangyan.financeapp import android.os.Binder import androidx.appcompat.app.AppCompatActivity import android.os.Bundle import com.sangyan.financeapp.databinding.ActivityViewNewsBinding class ViewNews : AppCompatActivity() { // Initializing variables private lateinit var newsUrl : String private lateinit var binding: ActivityViewNewsBinding override fun onCreate(savedInstanceState: Bundle?) { super .onCreate(savedInstanceState) // Inflating the layout of the activity // using the ActivityViewNewsBinding binding = ActivityViewNewsBinding.inflate(layoutInflater) setContentView(binding.root) // Getting the news URL from the intent sent by MainActivity newsUrl = intent.getStringExtra(MainActivity.URL).toString() // Loading the news article in the webview binding.webView.loadUrl(newsUrl) } } |
Java
package com.sangyan.financeapp; import android.content.Intent; import android.os.Bundle; import androidx.appcompat.app.AppCompatActivity; import com.sangyan.financeapp.databinding.ActivityViewNewsBinding; public class ViewNews extends AppCompatActivity { // Initializing variables private String newsUrl; private ActivityViewNewsBinding binding; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // Inflating the layout of the activity // using the ActivityViewNewsBinding binding = ActivityViewNewsBinding.inflate( getLayoutInflater()); setContentView(binding.getRoot()); // Getting the news URL from the intent sent by // MainActivity newsUrl = getIntent().getStringExtra( MainActivity.Companion.getURL()); // Loading the news article in the webview binding.webView.loadUrl(newsUrl); } } |
Output:
Contact Us