Reimplementing CrytpoNews in Native Android(UPDATE: Storing News in DB)

in #utopian-io4 years ago (edited)

Recently, @mathemandy and I started working on re-implementing the CryptoNews app built by @Johnesan in native android.

The aim of the app is to fetch news from different wordpress websites and display in the application. While the news are displayed, the app gives you the ability to click and view each news within the app. Instead of taking you out of the app, it gives you the comfort of viewing within the app.

History

Update

  • Storing of news data in the database

In the previous implementation, the news were fetched directly from internet and displayed. But anytime the app is restarted or the device screen is rotated, the news are re-fetched from the source, this is a bad user-experience and also wasteful because this consumes more of the user's mobile data.

So, the proper way to handle this is persisting the data .i.e storing the news in the database. How will that be accomplished? In 2017, Google released a new persisting component called Room.

videotogif_2018.03.20_15.25.47.gif

From the above image, the news are fetched from the internet and while it's getting data, it displays a Progress Bar to indicate to the user that something is happening. At the restart of the app, the news are fetched from the database. You could notice the mobile data is switched off to confirm that it's actually working.

This is how it was implemented

  • Firstly, the News POJO class was annotated with @Entity(tableName = "news")
    The @Entity represents a table within the db.
@Entity(tableName = "news")
public class News{

    @SerializedName("date")
    private String date;

    @SerializedName("link")
    private String link;

    @SerializedName("title")
    private Title title;

    @SerializedName("_embedded")
    private Embedded embedded;

    @NonNull
    @PrimaryKey()
    @SerializedName("id")
    private int id;

    @SerializedName("guid")
    private Guid guid;

        ...
}
  • Secondly, an interface class called @Dao was created. This contains the methods used in accessing the db like @Insert, @Query, @Delete, etc.
@Dao
public interface NewsDao {

    @Insert(onConflict = REPLACE)
    void save(News news);

    @Query("SELECT * FROM news")
    LiveData<List<News>> queryNews();

}
  • Thirdly, an abstract class @Database was created as the main access point for the underlying connection to the app's persisted data.
@Database(entities = {News.class}, version = 1)
@TypeConverters({Converters.class})
public abstract class NewsDatabase extends RoomDatabase {
    public abstract NewsDao newsDao();
}
  • Fourthly, in the repository class, all logic is done in fetching and storing data.
    The setupNewsListener method is called to check if more news should be fetched in order to update the UI. Else it fetches the stored data from the db.
 public class NewsRepository {

    @Inject
    NewsApiService newsApiService;

    @Inject
    PreferenceUtils preferenceUtils;

    private Executor diskExecutor;
    private Executor mainThreadExecutor;
    AppExecutors appExecutors;
    NewsDao newsDao;

    private MediatorLiveData<List<News>> newsLiveData = new MediatorLiveData<>();
    private LiveData<List<News>> newdDbSource;
    private static  final String TAG = NewsRepository.class.getSimpleName();



    @Inject
    NewsRepository(AppExecutors appExecutors, NewsDao newsDao) {
        this.appExecutors = appExecutors;
        diskExecutor = appExecutors.getDiskExecutor();
        mainThreadExecutor = appExecutors.getMainThreadExecutor();
        this.newsDao = newsDao;
        newdDbSource = loadUserNewsFromDb();
        setupNewsListener();

    }

    @WorkerThread
    private LiveData<List<News>> loadUserNewsFromDb() {
        Log.d("TAG", "Load User Deals from DB");
        return  newsDao.queryNews();
    }

    public void getAllNews() {

        Observable<List<News>> getNews = NetworkModule.api.getLatestNews().subscribeOn(Schedulers.io());
        Observable<List<News>> getNews2 = NetworkModule.api2.getLatestNews().subscribeOn(Schedulers.io());
        Observable<List<News>> getNews3 = NetworkModule.api3.getLatestNews().subscribeOn(Schedulers.io());
        Observable<List<News>> getNews4 = NetworkModule.api4.getLatestNews().subscribeOn(Schedulers.io());

        Observable.zip(getNews, getNews2, getNews3, getNews4,
                new Function4<List<News>, List<News>, List<News>, List<News>, List<News>>() {
                    @Override
                    public List<News> apply(List<News> news, List<News> news2, List<News> news3, List<News> news4) throws Exception {
                        List<News> newsList = new ArrayList<>();
                        for (News newsObj : news) {
                            newsList.add(newsObj);
                        }
                        for (News newsObj : news2) {
                            newsList.add(newsObj);
                        }
                        for (News newsObj : news3) {
                            newsList.add(newsObj);
                        }
                        for (News newsObj : news4) {
                            newsList.add(newsObj);
                        }

                        return newsList;
                    }
                })
                // Run on a background thread
                .subscribeOn(Schedulers.io())
                // Be notified on the main thread
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<List<News>>() {
                               @Override
                               public void onSubscribe(Disposable d) {

                               }

                               @Override
                               public void onNext(List<News> newsList) {
                                    getNewsForuser(newsList);

                               }

                               @Override
                               public void onError(Throwable e) {

                               }

                               @Override
                               public void onComplete() {

                               }
                           });
    }


    private void getNewsForuser(List<News> newsList) {
        Log.e(TAG, "Loading News for user was successful");
        Log.e(TAG, "Now Saving");
        preferenceUtils.storeTimeNewswasReceived();
        diskExecutor.execute(() -> {
            saveUserDealsTODb(newsList);
            mainThreadExecutor.execute(() -> {
                newsLiveData.addSource(loadUserNewsFromDb(), this::setValueNews);
            });
        });

    }


    @WorkerThread
    private void saveUserDealsTODb(List<News> newsList) {
        Log.d("TAG", "Saving User Deals to DB");

            for (News news : newsList){
                newsDao.save(news);
            }

    }

    private void fetchFromServer(){
        Log.d("TAG", "Fetching Deals From Server");
        getAllNews();
    }

    private void setupNewsListener(){
        newsLiveData.addSource(newdDbSource,  newsList -> {
            Log.d("TAG", "News Livedata Listener");
            if (shouldFetchNews(newsList)){
                fetchFromServer();
            }else {
                setValueNews(newsList);
            }
        });

    }

    private void setValueNews(List<News> newsValue) {
        if (newsValue != null){
            mainThreadExecutor.execute(() -> newsLiveData.setValue(newsValue));
        }
    }

    private boolean shouldFetchNews(List<News> newsList){
        long lastFetched = preferenceUtils.getLastTimeNewsWasStored();
        long timeOut = TimeUnit.MINUTES.toMillis(30) + lastFetched;
        return  newsList.size() <1 || Calendar.getInstance().getTimeInMillis() > timeOut;
    }

    public LiveData<List<News>> asLiveData(){
        return newsLiveData;
    }

}

IMPORTANT RESOURCES

Github Pull Request: https://github.com/Johnesan/CryptoNews/pull/3

Android Apk File: https://github.com/Johnesan/CryptoNews/releases/download/1.0/CryptoNews.apk

Roadmap

  • Searching all news
  • Persisting user news
  • Providing different layouts
  • Providing different themes for user
  • Push Notifications
  • News Posts sharing
  • Incorporating more news and giving the user the flexibility of deciding what he wants to read per time.



Posted on Utopian.io - Rewarding Open Source Contributors

Sort:  

Thank you for the contribution. It has been approved.

Great post, very descriptive! Keep up the good work!

You can contact us on Discord.
[utopian-moderator]

Hey @princessdharmy I am @utopian-io. I have just upvoted you!

Achievements

  • You have less than 500 followers. Just gave you a gift to help you succeed!
  • Seems like you contribute quite often. AMAZING!

Community-Driven Witness!

I am the first and only Steem Community-Driven Witness. Participate on Discord. Lets GROW TOGETHER!

mooncryption-utopian-witness-gif

Up-vote this comment to grow my power and help Open Source contributions like this one. Want to chat? Join me on Discord https://discord.gg/Pc8HG9x