Skip to content

Instantly share code, notes, and snippets.

@haerulmuttaqin
Created March 22, 2021 09:00
Show Gist options
  • Save haerulmuttaqin/d1b6aa12ef12749ce2b1c4b4b46c5f6e to your computer and use it in GitHub Desktop.
Save haerulmuttaqin/d1b6aa12ef12749ce2b1c4b4b46c5f6e to your computer and use it in GitHub Desktop.
#6 — Job Finder App (https://youtu.be/TpFe8WSCzBo)
<?xml version="1.0" encoding="utf-8"?>
<layout 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">
<data>
<import type="android.view.View"/>
<variable
name="item"
type="id.haerulmuttaqin.jobfinder.data.entity.GithubJob" />
<variable
name="state"
type="id.haerulmuttaqin.jobfinder.data.entity.NetworkState" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/blue_200">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/blue_200"
app:elevation="0dp">
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/Theme.JobFinder.Toolbar"
app:elevation="0dp"
app:layout_collapseMode="pin"
app:popupTheme="@style/Theme.JobFinder.PopupOverlay"
app:titleTextColor="@color/white" />
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_rounded_top"
android:fillViewport="true"
android:fitsSystemWindows="true"
app:refreshing="@{state != null &amp;&amp; state.getStatus().equals(state.Status.FIRST_LOADING) ? true : false}"
app:layout_anchor="@id/appbar"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_rounded_top"
android:fillViewport="true"
android:fitsSystemWindows="true"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<ImageView
android:id="@+id/imgtop"
android:layout_width="40dp"
android:layout_height="5dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="8dp"
android:background="@drawable/bg_strip_round" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_marginTop="15dp"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
<LinearLayout
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fillViewport="true"
android:clickable="false"
android:focusable="false"
android:visibility="@{state != null &amp;&amp; state.getStatus().equals(state.Status.FIRST_FAILED) ? View.VISIBLE : View.INVISIBLE}"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_marginTop="100dp"
android:src="@drawable/ic_undraw_empty" />
<TextView
android:id="@+id/textEmpty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="No Data Found"
android:fontFamily="@font/medium"/>
<TextView
android:id="@+id/textEmptyErr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="16dp"
android:paddingBottom="16dp"
android:paddingEnd="16dp"
android:text="@{state.msg}"
android:fontFamily="@font/book"/>
</LinearLayout>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="16dp"
android:visibility="@{state != null &amp;&amp; state.getStatus().equals(state.Status.FIRST_LOADING) ? View.VISIBLE : View.INVISIBLE}"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:shimmer_duration="800"
app:shimmer_auto_start="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<ImageView
android:id="@+id/shadow"
android:layout_width="match_parent"
android:layout_height="36dp"
android:layout_marginStart="3dp"
android:layout_marginEnd="3dp"
android:layout_marginTop="15dp"
android:src="@drawable/bg_shadow_up_to_down"
app:layout_anchor="@+id/scroll"
app:layout_anchorGravity="top|center" />
</RelativeLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="viewModel"
type="id.haerulmuttaqin.jobfinder.ui.MainViewModel" />
</data>
<androidx.coordinatorlayout.widget.CoordinatorLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/blue_200">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:elevation="0dp">
<com.google.android.material.appbar.CollapsingToolbarLayout
android:id="@+id/collapse"
android:layout_width="match_parent"
android:layout_height="280dp"
app:collapsedTitleTextAppearance="@style/collapsedTitleStyle"
app:contentScrim="?attr/colorPrimary"
app:expandedTitleMarginBottom="26dp"
app:expandedTitleMarginStart="26dp"
app:expandedTitleTextAppearance="@style/expandedTitleStyle"
app:layout_scrollFlags="scroll|exitUntilCollapsed|snap"
app:title="@string/app_name"
app:titleEnabled="true">
<RelativeLayout
app:layout_collapseMode="parallax"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/banner"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/banner_1" />
<TextView
android:id="@+id/welcomeText"
android:text="Hi, Welcome back"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="26dp"
android:layout_marginTop="40dp"
android:layout_marginEnd="10dp"
android:fontFamily="@font/book"
android:textSize="16sp"
android:textColor="@color/white"
android:layout_toStartOf="@id/notif"/>
<TextView
android:id="@+id/secondaryText"
android:text="Find your perfect job"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/welcomeText"
android:layout_marginStart="26dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="10dp"
android:layout_toStartOf="@id/notif"
android:textSize="26sp"
android:textColor="@color/white"
android:fontFamily="@font/bold" />
<ImageView
android:id="@+id/notif"
android:layout_width="26dp"
android:layout_height="40dp"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_marginTop="42dp"
android:layout_marginEnd="16dp"
android:contentDescription="@string/app_name"
android:src="@drawable/ic_baseline_more_vert_24"
android:paddingBottom="10dp"
app:tint="@color/white"/>
<LinearLayout
android:id="@+id/searchLayout"
android:orientation="vertical"
android:layout_alignParentBottom="true"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.card.MaterialCardView
android:id="@+id/card_search"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:layout_marginRight="20dp"
android:layout_marginBottom="60dp"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackground"
app:strokeWidth="0.3dp"
app:cardCornerRadius="20dp"
app:cardElevation="0dp"
app:strokeColor="@color/blue_300"
app:cardBackgroundColor="@color/blue_250">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="60dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="What are you looking for?"
android:singleLine="true"
android:layout_centerVertical="true"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:textColor="@color/blue_050"
android:fontFamily="@font/book"
android:textSize="16dp"/>
<ImageView
android:src="@drawable/ic_round_search_24"
android:layout_width="25dp"
android:layout_height="60dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="16dp"
app:tint="@color/blue_050"/>
</RelativeLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</RelativeLayout>
<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:theme="@style/Theme.JobFinder.Toolbar"
android:fitsSystemWindows="true"
app:background="@android:color/transparent"
app:layout_collapseMode="pin"
app:popupTheme="@style/Theme.JobFinder.PopupOverlay"
app:titleTextColor="@color/white" />
</com.google.android.material.appbar.CollapsingToolbarLayout>
</com.google.android.material.appbar.AppBarLayout>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/swipeRefresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_rounded_top"
android:fillViewport="true"
android:fitsSystemWindows="true"
app:layout_anchor="@id/appbar"
app:layout_behavior="com.google.android.material.appbar.AppBarLayout$ScrollingViewBehavior">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.core.widget.NestedScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:fillViewport="true">
<RelativeLayout
android:id="@+id/contentLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/markedTitle"
android:text="Marked Job"
android:textSize="16dp"
android:textColor="@color/gray_600"
android:layout_marginStart="21dp"
android:layout_marginEnd="21dp"
android:layout_marginTop="25dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fontFamily="@font/medium"/>
<TextView
android:id="@+id/showAllMarked"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:text="Show all"
android:textSize="14dp"
android:textColor="@color/gray_300"
android:layout_marginStart="21dp"
android:layout_marginEnd="21dp"
android:layout_marginTop="25dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:fontFamily="@font/book"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewMarked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="6dp"
android:layout_marginBottom="10dp"
android:layout_below="@id/markedTitle"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:nestedScrollingEnabled="true"
android:orientation="horizontal"/>
<TextView
android:id="@+id/recommendedTitle"
android:text="Recommended Job"
android:textSize="16dp"
android:textColor="@color/gray_600"
android:layout_marginStart="21dp"
android:layout_marginEnd="21dp"
android:layout_marginTop="25dp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_below="@id/recyclerViewMarked"
android:fontFamily="@font/medium"/>
<TextView
android:id="@+id/showAllRecommended"
android:clickable="true"
android:focusable="true"
android:background="?attr/selectableItemBackgroundBorderless"
android:text="Show all"
android:textSize="14dp"
android:textColor="@color/gray_300"
android:layout_marginStart="21dp"
android:layout_marginEnd="21dp"
android:layout_marginTop="25dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentEnd="true"
android:layout_below="@id/recyclerViewMarked"
android:fontFamily="@font/book"/>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginStart="5dp"
android:layout_marginEnd="5dp"
android:layout_below="@id/recommendedTitle"
android:layout_marginBottom="20dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</RelativeLayout>
</androidx.core.widget.NestedScrollView>
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmer"
android:visibility="gone"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:layout_marginTop="16dp"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:shimmer_duration="800"
app:shimmer_auto_start="true">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
<include layout="@layout/item_job_placeholder" />
</LinearLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<LinearLayout
android:id="@+id/emptyView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fillViewport="true"
android:clickable="false"
android:focusable="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="250dp"
android:layout_height="250dp"
android:layout_gravity="center"
android:layout_marginTop="100dp"
android:src="@drawable/ic_undraw_empty" />
<TextView
android:id="@+id/textEmpty"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="16dp"
android:paddingTop="16dp"
android:paddingEnd="16dp"
android:text="No Data Found"
android:fontFamily="@font/medium"/>
<TextView
android:id="@+id/textEmptyErr"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:paddingStart="16dp"
android:paddingBottom="16dp"
android:paddingEnd="16dp"
android:fontFamily="@font/book"/>
</LinearLayout>
<ImageView
android:id="@+id/shadow"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginTop="16dp"
android:src="@drawable/bg_shadow_up_to_down"
android:visibility="visible" />
<ImageView
android:background="@drawable/bg_strip_round"
android:layout_width="40dp"
android:layout_height="5dp"
android:layout_marginTop="8dp"
android:layout_centerHorizontal="true"/>
</RelativeLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>
import java.util.List;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
public interface ApiInterface {
@GET("positions.json")
Call<List<GithubJob>> getJobList();
@GET("positions.json")
Call<List<GithubJob>> getJobList(@Query("page") int page);
@GET("positions.json")
Call<List<GithubJob>> searchJobList(@Query("search") String keyword);
@GET("positions.json")
Call<List<GithubJob>> searchJobList(@Query("page") int page, @Query("search") String keyword);
}
import java.util.List;
import javax.inject.Inject;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import retrofit2.Call;
public class ConnectionServer {
@Inject
ApiInterface apiInterface;
public ConnectionServer(ApiInterface apiInterface) {
this.apiInterface = apiInterface;
}
public Call<List<GithubJob>> getJobList() {
return apiInterface.getJobList();
}
public Call<List<GithubJob>> getJobListByPage(int page) {
return apiInterface.getJobList(page);
}
public Call<List<GithubJob>> searchJobList(String keyword) {
return apiInterface.searchJobList(keyword);
}
public Call<List<GithubJob>> searchJobListByPage(int page, String keyword) {
return apiInterface.searchJobList(page, keyword);
}
}
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import androidx.paging.DataSource;
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository;
import io.reactivex.subjects.ReplaySubject;
public class GithubDataSourceFactory extends DataSource.Factory {
private MutableLiveData<GithubPageKeyedDataSource> networkStatus;
private GithubPageKeyedDataSource githubPageKeyedDataSource;
public GithubDataSourceFactory(ConnectionServer connectionServer, GithubJobRepository repository, String keyword) {
this.networkStatus = new MutableLiveData<>();
githubPageKeyedDataSource = new GithubPageKeyedDataSource(connectionServer, repository, keyword);
}
@NonNull
@Override
public DataSource create() {
networkStatus.postValue(githubPageKeyedDataSource);
return githubPageKeyedDataSource;
}
public MutableLiveData<GithubPageKeyedDataSource> getNetworkStatus() {
return networkStatus;
}
public ReplaySubject<GithubJob> getData() {
return githubPageKeyedDataSource.getGithubJobObserve();
}
}
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import com.google.gson.annotations.Expose;
import com.google.gson.annotations.SerializedName;
import java.io.Serializable;
@Entity
public class GithubJob implements Serializable {
@PrimaryKey
@SerializedName("id")
@Expose
@NonNull
public String id;
@SerializedName("type")
@Expose
public String type;
@SerializedName("url")
@Expose
public String url;
@SerializedName("created_at")
@Expose
public String createdAt;
@SerializedName("company")
@Expose
public String company;
@SerializedName("company_url")
@Expose
public String companyUrl;
@SerializedName("location")
@Expose
public String location;
@SerializedName("title")
@Expose
public String title;
@SerializedName("description")
@Expose
public String description;
@SerializedName("how_to_apply")
@Expose
public String howToApply;
@SerializedName("company_logo")
@Expose
public String companyLogo;
public int is_mark;
public static DiffUtil.ItemCallback<GithubJob> DIFF_CALLBACK = new DiffUtil.ItemCallback<GithubJob>() {
@Override
public boolean areItemsTheSame(@NonNull GithubJob oldItem, @NonNull GithubJob newItem) {
return oldItem.id.equals(newItem.id);
}
@Override
public boolean areContentsTheSame(@NonNull GithubJob oldItem, @NonNull GithubJob newItem) {
return oldItem.id.equals(newItem.id);
}
};
}
import androidx.lifecycle.LiveData;
import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query;
import java.util.List;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
@Dao
public interface GithubJobDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(GithubJob githubJob);
@Query("SELECT * FROM githubjob ORDER BY createdAt DESC LIMIT 10")
LiveData<List<GithubJob>> getLiveData();
@Query("SELECT * FROM githubjob ORDER BY createdAt DESC")
List<GithubJob> getList();
@Query("SELECT * FROM githubjob WHERE LOWER(title) LIKE :keyword OR LOWER(description) LIKE :keyword ORDER BY createdAt DESC")
LiveData<List<GithubJob>> searchLiveData(String keyword);
@Query("SELECT * FROM githubjob WHERE LOWER(title) LIKE :keyword OR LOWER(description) LIKE :keyword ORDER BY createdAt DESC")
List<GithubJob> searchList(String keyword);
@Query("SELECT * FROM githubjob where is_mark = 1 ORDER BY createdAt DESC")
LiveData<List<GithubJob>> getLiveDataMarked();
@Query("SELECT * FROM githubjob where id = :id")
GithubJob getDataById(String id);
}
import android.content.Context;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import id.haerulmuttaqin.jobfinder.Utils;
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.entity.NetworkState;
import id.haerulmuttaqin.jobfinder.data.paging.GithubDataSourceFactory;
import id.haerulmuttaqin.jobfinder.data.paging.GithubNetwork;
import id.haerulmuttaqin.jobfinder.data.paging.LocalDataSourceFactory;
import io.reactivex.schedulers.Schedulers;
public class GithubJobRepository {
private static GithubJobDatabase database;
private static GithubJobRepository repository;
private static GithubNetwork network;
private MediatorLiveData githubMediatorLiveData;
private LiveData<PagedList<GithubJob>> pagedListLiveData;
public GithubJobRepository(Context context) {
database = GithubJobDatabase.getDatabase(context);
}
public static GithubJobRepository getInstance(Context context) {
if (repository == null) {
repository = new GithubJobRepository(context);
}
return repository;
}
public void updateMarkJob(GithubJob githubJob) {
githubJob.is_mark = githubJob.is_mark == 1 ? 0 : 1;
database.githubJobDao().insert(githubJob);
}
public void insert(GithubJob githubJob) {
if (database.githubJobDao().getDataById(githubJob.id) == null) {
database.githubJobDao().insert(githubJob);
}
}
public GithubJob getById(String id) {
return database.githubJobDao().getDataById(id);
}
public LiveData<List<GithubJob>> getLiveData() {
return database.githubJobDao().getLiveData();
}
public LiveData<List<GithubJob>> searchLiveData(String keyword) {
keyword = "%" + keyword + "%";
return database.githubJobDao().searchLiveData(keyword);
}
public LiveData<List<GithubJob>> getLiveDataMarked() {
return database.githubJobDao().getLiveDataMarked();
}
/*PAGING*/
public LiveData<PagedList<GithubJob>> getPagedListLiveData() {
return pagedListLiveData;
}
private PagedList.BoundaryCallback<GithubJob> boundaryCallback = new PagedList.BoundaryCallback<GithubJob>() {
@Override
public void onZeroItemsLoaded() {
super.onZeroItemsLoaded();
githubMediatorLiveData.addSource(getPagedListLiveData(), value -> {
githubMediatorLiveData.setValue(value);
githubMediatorLiveData.removeSource(getPagedListLiveData());
});
}
};
public void initPageDao(String keyword) {
PagedList.Config config = (new PagedList.Config.Builder()).setEnablePlaceholders(false)
.setInitialLoadSizeHint(Integer.MAX_VALUE).setPageSize(Integer.MAX_VALUE).build();
Executor executor = Executors.newFixedThreadPool(3);
LocalDataSourceFactory localDataSourceFactory = new LocalDataSourceFactory(database.githubJobDao(), keyword);
LivePagedListBuilder livePagedListBuilder = new LivePagedListBuilder(localDataSourceFactory, config);
pagedListLiveData = livePagedListBuilder.setFetchExecutor(executor).build();
}
public LiveData<PagedList<GithubJob>> getDataByPage(ConnectionServer connectionServer, GithubJobRepository repository, String keyword) {
initPageDao(keyword);
GithubDataSourceFactory githubDataSourceFactory = new GithubDataSourceFactory(connectionServer, repository, keyword);
network = new GithubNetwork(githubDataSourceFactory, boundaryCallback);
githubMediatorLiveData = new MediatorLiveData<>();
githubMediatorLiveData.addSource(network.getPagedListLiveData(), value -> {
githubMediatorLiveData.setValue(value);
});
githubDataSourceFactory.getData()
.observeOn(Schedulers.io())
.subscribe(item -> {
item.createdAt = Utils.dateFormatter(item.createdAt);
database.githubJobDao().insert(item);
});
return githubMediatorLiveData;
}
public LiveData<NetworkState> getNetworkState() {
return network.getNetworkStateLiveData();
}
}
import androidx.arch.core.util.Function;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Transformations;
import androidx.paging.LivePagedListBuilder;
import androidx.paging.PagedList;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.entity.NetworkState;
public class GithubNetwork {
final private LiveData<PagedList<GithubJob>> pagedListLiveData;
final private LiveData<NetworkState> networkStateLiveData;
public GithubNetwork(GithubDataSourceFactory githubDataSourceFactory, PagedList.BoundaryCallback<GithubJob> boundaryCallback) {
PagedList.Config config = (new PagedList.Config.Builder())
.setEnablePlaceholders(false)
.setInitialLoadSizeHint(1)
.setPageSize(50)
.build();
this.networkStateLiveData = Transformations.switchMap(
githubDataSourceFactory.getNetworkStatus(),
(Function<GithubPageKeyedDataSource, LiveData<NetworkState>>) GithubPageKeyedDataSource::getNetworkState);
Executor executor = Executors.newFixedThreadPool(3);
LivePagedListBuilder livePagedListBuilder = new LivePagedListBuilder(githubDataSourceFactory, config);
this.pagedListLiveData = livePagedListBuilder.setFetchExecutor(executor).setBoundaryCallback(boundaryCallback).build();
}
public LiveData<NetworkState> getNetworkStateLiveData() {
return networkStateLiveData;
}
public LiveData<PagedList<GithubJob>> getPagedListLiveData() {
return pagedListLiveData;
}
}
import androidx.annotation.NonNull;
import androidx.lifecycle.MutableLiveData;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import id.haerulmuttaqin.jobfinder.Utils;
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.entity.NetworkState;
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository;
import io.reactivex.subjects.ReplaySubject;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class GithubPageKeyedDataSource extends androidx.paging.PageKeyedDataSource<String, GithubJob> {
private final MutableLiveData networkState;
private final ReplaySubject<GithubJob> githubJobObserve;
private final ConnectionServer connectionServer;
private final GithubJobRepository repository;
private final String keyword;
public GithubPageKeyedDataSource(ConnectionServer connectionServer, GithubJobRepository repository, String keyword) {
this.networkState = new MutableLiveData();
this.connectionServer = connectionServer;
this.repository = repository;
this.githubJobObserve = ReplaySubject.create();
this.keyword = keyword;
}
public MutableLiveData getNetworkState() {
return networkState;
}
public ReplaySubject<GithubJob> getGithubJobObserve() {
return githubJobObserve;
}
@Override
public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<String, GithubJob> callback) {
networkState.postValue(NetworkState.FIRST_LOADING);
Call<List<GithubJob>> call = null;
if (keyword != null) {
call = connectionServer.searchJobListByPage(1, keyword);
} else {
call = connectionServer.getJobListByPage(1);
}
call.enqueue(new Callback<List<GithubJob>>() {
@Override
public void onResponse(Call<List<GithubJob>> call, Response<List<GithubJob>> response) {
if (response.isSuccessful()) {
networkState.postValue(NetworkState.LOADED);
callback.onResult(response.body(), Integer.toString(1), Integer.toString(2));
if (response.body().size() > 0) {
for (GithubJob item : response.body()) {
githubJobObserve.onNext(item);
}
} else {
networkState.postValue(new NetworkState(NetworkState.Status.FIRST_FAILED, "No data found"));
}
} else {
networkState.postValue(new NetworkState(NetworkState.Status.FIRST_FAILED, response.message()));
}
}
@Override
public void onFailure(Call<List<GithubJob>> call, Throwable t) {
networkState.postValue(new NetworkState(NetworkState.Status.FIRST_FAILED, Utils.errorMessageHandler(call, t)));
callback.onResult(new ArrayList<>(), Integer.toString(1), Integer.toString(2));
}
});
}
@Override
public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, GithubJob> callback) {}
@Override
public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, GithubJob> callback) {
networkState.postValue(NetworkState.LOADING);
final AtomicInteger page = new AtomicInteger(0);
try {
page.set(Integer.parseInt(params.key));
} catch (NumberFormatException e) {
e.printStackTrace();
}
Call<List<GithubJob>> call = null;
if (keyword != null) {
call = connectionServer.searchJobListByPage(page.get(), keyword);
} else {
call = connectionServer.getJobListByPage(page.get());
}
call.enqueue(new Callback<List<GithubJob>>() {
@Override
public void onResponse(Call<List<GithubJob>> call, Response<List<GithubJob>> response) {
if (response.isSuccessful()) {
networkState.postValue(NetworkState.LOADED);
callback.onResult(response.body(), Integer.toString( page.get() + 1));
if (response.body().size() > 0) {
for (GithubJob item : response.body()) {
githubJobObserve.onNext(item);
}
}
} else {
networkState.postValue(new NetworkState(NetworkState.Status.FAILED, response.message()));
}
}
@Override
public void onFailure(Call<List<GithubJob>> call, Throwable t) {
networkState.postValue(new NetworkState(NetworkState.Status.FAILED, Utils.errorMessageHandler(call, t)));
callback.onResult(new ArrayList<>(), Integer.toString(page.get()));
}
});
}
}
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:app="http://schemas.android.com/apk/res-auto">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="10dp"
android:orientation="vertical">
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/shimmer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:shimmer_duration="800"
app:shimmer_auto_start="true">
<RelativeLayout
android:id="@+id/progress_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_marginTop="10dp" >
<View
android:id="@+id/icon"
android:layout_width="60dp"
android:layout_height="60dp"
android:gravity="center"
android:layout_gravity="center"
android:layout_marginStart="10dp"
android:background="@drawable/bg_photo_item" />
<View
android:id="@+id/title"
android:layout_marginStart="16dp"
android:layout_toEndOf="@id/icon"
android:layout_width="180dp"
android:layout_height="17dp"
android:layout_marginTop="2dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_strip_round" />
<View
android:id="@+id/subtitle"
android:layout_below="@id/title"
android:layout_marginStart="16dp"
android:layout_marginEnd="130dp"
android:layout_width="110dp"
android:layout_toEndOf="@id/icon"
android:layout_height="10dp"
android:layout_marginTop="10dp"
android:background="@drawable/bg_strip_round" />
<View
android:layout_below="@id/subtitle"
android:layout_marginStart="16dp"
android:layout_marginEnd="130dp"
android:layout_marginBottom="20dp"
android:layout_width="140dp"
android:layout_toEndOf="@id/icon"
android:layout_height="10dp"
android:layout_marginTop="5dp"
android:background="@drawable/bg_strip_round" />
</RelativeLayout>
</com.facebook.shimmer.ShimmerFrameLayout>
<TextView
android:visibility="gone"
android:id="@+id/error_msg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="20dp"
android:layout_gravity="center_horizontal" />
</LinearLayout>
</layout>
import android.content.Intent;
import android.os.Bundle;
import android.view.MenuItem;
import android.view.View;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.snackbar.Snackbar;
import javax.inject.Inject;
import id.haerulmuttaqin.jobfinder.R;
import id.haerulmuttaqin.jobfinder.base.BaseActivity;
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository;
import id.haerulmuttaqin.jobfinder.databinding.ActivityListBinding;
import id.haerulmuttaqin.jobfinder.ui.MainAdapter;
import id.haerulmuttaqin.jobfinder.ui.MainViewModel;
import id.haerulmuttaqin.jobfinder.ui.detail.DetailActivity;
public class ListActivity extends BaseActivity<ActivityListBinding, MainViewModel> implements MainViewModel.Navigator {
@Inject
GithubJobRepository repository;
@Inject
ConnectionServer server;
private ActivityListBinding binding;
private MainViewModel viewModel;
@Override
public int getBindingVariable() {
return 0;
}
@Override
public int getLayoutId() {
return R.layout.activity_list;
}
@Override
public MainViewModel getViewModel() {
return viewModel;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = getViewDataBinding();
viewModel = new ViewModelProvider(
this,
new MainViewModel.ModelFactory(this, server, repository)
).get(MainViewModel.class);
viewModel.setNavigator(this);
if (getIntent().getStringExtra("recommended") != null) {
setupActionBar("Recommended Jobs");
getData(null);
binding.swipeRefresh.setOnRefreshListener(()->getData(null));
}
else if (getIntent().getStringExtra("search") != null) {
String keyword = getIntent().getStringExtra("search");
setupActionBar("Search Jobs (" + keyword + ")");
getData(keyword);
binding.swipeRefresh.setOnRefreshListener(()->getData(keyword));
}
else if (getIntent().getStringExtra("marked") != null) {
setupActionBar("Marked Jobs");
binding.swipeRefresh.setOnRefreshListener(null);
viewModel.getLiveDataMarked().observe(this, githubJobs -> {
hideProgress();
if (githubJobs.size() > 0) {
binding.recyclerView.setAdapter(new MainAdapter(githubJobs, viewModel));
} else {
binding.emptyView.setVisibility(View.VISIBLE);
binding.textEmptyErr.setText("No job marked!");
}
});
}
else {
Toast.makeText(this, "Failed get list!", Toast.LENGTH_SHORT).show();
finish();
}
}
private void getData(String keyword) {
final ListPagedAdapter adapter = new ListPagedAdapter(viewModel);
viewModel.getPagedData(keyword).observe(this, githubJobs -> {
adapter.submitList(githubJobs);
});
viewModel.getNetworkState().observe(this, networkState -> {
adapter.setNetworkState(networkState);
binding.setState(networkState);
});
binding.recyclerView.setAdapter(adapter);
}
private void setupActionBar(String title) {
setSupportActionBar(binding.toolbar);
if (getSupportActionBar() != null) {
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setTitle(title);
}
}
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == android.R.id.home) {
onBackPressed();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public void showProgress() {
binding.swipeRefresh.setRefreshing(true);
binding.emptyView.setVisibility(View.GONE);
binding.shimmer.setVisibility(View.VISIBLE);
binding.shimmer.startShimmer();
binding.recyclerView.setVisibility(View.GONE);
}
@Override
public void hideProgress() {
binding.swipeRefresh.setRefreshing(false);
binding.emptyView.setVisibility(View.GONE);
binding.shimmer.setVisibility(View.GONE);
binding.shimmer.stopShimmer();
binding.recyclerView.setVisibility(View.VISIBLE);
}
@Override
public void onGetResult(boolean status, String message) {
if (!status) { //<-- status result is FALSE
binding.textEmptyErr.setText(message);
binding.emptyView.setVisibility(View.VISIBLE);
} else {
binding.emptyView.setVisibility(View.GONE);
}
}
@Override
public void onMark(int mark, String title) {
Snackbar.make(binding.getRoot(), mark == 0 ? "\uD83D\uDE13 Unmark " + title : "\uD83D\uDE0D Marked " + title, Snackbar.LENGTH_SHORT).show();
}
@Override
public void onItemClick(GithubJob githubJob) {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("item", githubJob);
startActivity(intent);
}
}
import android.annotation.SuppressLint;
import android.graphics.drawable.Drawable;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.databinding.DataBindingUtil;
import androidx.paging.PagedListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.load.DataSource;
import com.bumptech.glide.load.engine.GlideException;
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
import com.bumptech.glide.request.RequestListener;
import com.bumptech.glide.request.target.Target;
import id.haerulmuttaqin.jobfinder.R;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.entity.NetworkState;
import id.haerulmuttaqin.jobfinder.databinding.ItemJobBinding;
import id.haerulmuttaqin.jobfinder.databinding.ItemJobLoadingBinding;
import id.haerulmuttaqin.jobfinder.ui.MainViewModel;
public class ListPagedAdapter extends PagedListAdapter<GithubJob, RecyclerView.ViewHolder> {
private MainViewModel viewModel;
private NetworkState networkState;
public ListPagedAdapter(MainViewModel viewModel) {
super(GithubJob.DIFF_CALLBACK);
this.viewModel = viewModel;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == R.layout.item_job) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ItemJobBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_job, parent, false);
return new RecyclerViewAdapter(binding);
}
else if (viewType == R.layout.item_job_loading) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
ItemJobLoadingBinding binding = DataBindingUtil.inflate(inflater, R.layout.item_job_loading, parent, false);
return new LoadingViewAdapter(binding);
}
else {
throw new IllegalArgumentException("unknown view type");
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
switch (getItemViewType(position)) {
case R.layout.item_job:
GithubJob item = getItem(position);
if (item != null) {
((RecyclerViewAdapter) holder).bind(item, viewModel);
}
break;
case R.layout.item_job_loading:
((LoadingViewAdapter) holder).bind(networkState);
break;
}
}
private boolean hasExtraRow() {
return networkState != null && networkState != NetworkState.LOADED;
}
@Override
public int getItemViewType(int position) {
if (hasExtraRow() && position == getItemCount() - 1) {
return R.layout.item_job_loading;
} else {
return R.layout.item_job;
}
}
public void setNetworkState(NetworkState networkState) {
this.networkState = networkState;
boolean previousExtraRow = hasExtraRow();
NetworkState previousState = this.networkState;
boolean newExtraRow = hasExtraRow();
if (previousExtraRow != newExtraRow) {
if (previousExtraRow) {
notifyItemRemoved(getItemCount());
}
else {
notifyItemInserted(getItemCount());
}
}
else if (newExtraRow && previousState != networkState) {
notifyItemChanged(getItemCount() - 1);
}
}
public static class RecyclerViewAdapter extends RecyclerView.ViewHolder {
ItemJobBinding binding;
public RecyclerViewAdapter(ItemJobBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
@SuppressLint("UseCompatLoadingForDrawables")
void bind(@NonNull GithubJob data, MainViewModel viewModel) {
Glide.with(binding.photoPreview.getContext())
.load(data.companyLogo)
.error(binding.photoPreview.getContext().getDrawable(R.drawable.ic_round_business_center_24))
.transition(DrawableTransitionOptions.withCrossFade())
.listener(new RequestListener<Drawable>() {
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Drawable> target, boolean isFirstResource) {
binding.progress.stopShimmer();
binding.progress.setVisibility(View.GONE);
return false;
}
@Override
public boolean onResourceReady(Drawable resource, Object model, Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
binding.progress.stopShimmer();
binding.progress.setVisibility(View.GONE);
return false;
}
})
.into(binding.photoPreview);
binding.setItem(data);
binding.setViewModel(viewModel);
binding.executePendingBindings();
}
}
public static class LoadingViewAdapter extends RecyclerView.ViewHolder {
ItemJobLoadingBinding binding;
public LoadingViewAdapter(ItemJobLoadingBinding binding) {
super(binding.getRoot());
this.binding = binding;
}
@SuppressLint("UseCompatLoadingForDrawables")
void bind(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);
}
binding.executePendingBindings();
}
}
}
import androidx.annotation.NonNull;
import androidx.paging.DataSource;
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobDao;
public class LocalDataSourceFactory extends DataSource.Factory {
private LocalPageKeyedDataSource localPageKeyedDataSource;
public LocalDataSourceFactory(GithubJobDao dao, String keyword) {
localPageKeyedDataSource = new LocalPageKeyedDataSource(dao, keyword);
}
@NonNull
@Override
public DataSource create() {
return localPageKeyedDataSource;
}
}
import androidx.annotation.NonNull;
import androidx.paging.PageKeyedDataSource;
import java.util.List;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobDao;
public class LocalPageKeyedDataSource extends PageKeyedDataSource<String, GithubJob> {
private final GithubJobDao dao;
private String keyword;
public LocalPageKeyedDataSource(GithubJobDao dao, String keyword) {
this.dao = dao;
this.keyword = keyword;
}
@Override
public void loadInitial(@NonNull LoadInitialParams<String> params, @NonNull LoadInitialCallback<String, GithubJob> callback) {
List<GithubJob> data = null;
if (keyword == null) {
data = dao.getList();
} else {
data = dao.searchList(keyword);
}
if (data.size() != 0) {
callback.onResult(data, Integer.toString(0), Integer.toString(1));
}
}
@Override
public void loadBefore(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, GithubJob> callback) {
}
@Override
public void loadAfter(@NonNull LoadParams<String> params, @NonNull LoadCallback<String, GithubJob> callback) {
}
}
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.RelativeLayout;
import androidx.annotation.Nullable;
import androidx.lifecycle.ViewModelProvider;
import com.google.android.material.bottomsheet.BottomSheetDialog;
import com.google.android.material.snackbar.Snackbar;
import com.google.android.material.textfield.TextInputEditText;
import javax.inject.Inject;
import id.haerulmuttaqin.jobfinder.R;
import id.haerulmuttaqin.jobfinder.base.BaseActivity;
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository;
import id.haerulmuttaqin.jobfinder.databinding.ActivityMainBinding;
import id.haerulmuttaqin.jobfinder.ui.detail.DetailActivity;
import id.haerulmuttaqin.jobfinder.ui.list.ListActivity;
public class MainActivity extends BaseActivity<ActivityMainBinding, MainViewModel> implements MainViewModel.Navigator {
@Inject ConnectionServer server;
@Inject GithubJobRepository repository;
private ActivityMainBinding binding;
private MainViewModel viewModel;
@Override
public int getBindingVariable() {
return 0;
}
@Override
public int getLayoutId() {
return R.layout.activity_main;
}
@Override
public MainViewModel getViewModel() {
return viewModel;
}
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = getViewDataBinding();
viewModel = new ViewModelProvider(this, new MainViewModel.ModelFactory(this, server, repository)).get(MainViewModel.class);
viewModel.setNavigator(this);
viewModel.getJobFromServer();
viewModel.getLiveData().observe(this, githubJobs -> {
binding.recyclerView.setAdapter(new MainAdapter(githubJobs, viewModel));
});
viewModel.getLiveDataMarked().observe(this, githubJobs -> {
if (githubJobs.size() > 0) {
binding.markedTitle.setVisibility(View.VISIBLE);
binding.recyclerViewMarked.setVisibility(View.VISIBLE);
binding.recyclerViewMarked.setAdapter(new MainMarkedAdapter(githubJobs, viewModel));
} else {
binding.markedTitle.setVisibility(View.GONE);
binding.recyclerViewMarked.setVisibility(View.GONE);
}
});
binding.swipeRefresh.setOnRefreshListener(()->viewModel.getJobFromServer());
binding.cardSearch.setOnClickListener(v->{
showDialogSearch();
});
binding.showAllMarked.setOnClickListener(v-> {
Intent intent = new Intent(this, ListActivity.class);
intent.putExtra("marked", "marked");
startActivity(intent);
});
binding.showAllRecommended.setOnClickListener(v-> {
Intent intent = new Intent(this, ListActivity.class);
intent.putExtra("recommended", "recommended");
startActivity(intent);
});
}
private void showDialogSearch() {
View view = getLayoutInflater().inflate(R.layout.dialog_search, null);
TextInputEditText m = view.findViewById(R.id.message);
RelativeLayout btnSearch = view.findViewById(R.id.search);
BottomSheetDialog dialog = new BottomSheetDialog(this, R.style.BottomSheetDialogStyle);
btnSearch.setOnClickListener(v -> {
Intent intent = new Intent(this, ListActivity.class);
intent.putExtra("search", m.getText().toString());
startActivity(intent);
});
m.requestFocus();
dialog.setContentView(view);
dialog.show();
}
@Override
public void showProgress() {
binding.swipeRefresh.setRefreshing(true);
binding.emptyView.setVisibility(View.GONE);
binding.shimmer.setVisibility(View.VISIBLE);
binding.shimmer.startShimmer();
binding.contentLayout.setVisibility(View.GONE);
}
@Override
public void hideProgress() {
binding.swipeRefresh.setRefreshing(false);
binding.emptyView.setVisibility(View.GONE);
binding.shimmer.setVisibility(View.GONE);
binding.shimmer.stopShimmer();
binding.contentLayout.setVisibility(View.VISIBLE);
}
@Override
public void onGetResult(boolean status, String message) {
if (!status) { //<-- status result is FALSE
binding.textEmptyErr.setText(message);
binding.emptyView.setVisibility(View.VISIBLE);
} else {
binding.emptyView.setVisibility(View.GONE);
}
}
@Override
public void onMark(int mark, String title) {
Snackbar.make(binding.getRoot(), mark == 0 ? "\uD83D\uDE13 Unmark " + title : "\uD83D\uDE0D Marked " + title, Snackbar.LENGTH_SHORT).show();
}
@Override
public void onItemClick(GithubJob githubJob) {
Intent intent = new Intent(this, DetailActivity.class);
intent.putExtra("item", githubJob);
startActivity(intent);
}
}
import android.content.Context;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import androidx.paging.PagedList;
import java.util.List;
import id.haerulmuttaqin.jobfinder.Utils;
import id.haerulmuttaqin.jobfinder.base.BaseViewModel;
import id.haerulmuttaqin.jobfinder.data.api.ConnectionServer;
import id.haerulmuttaqin.jobfinder.data.entity.GithubJob;
import id.haerulmuttaqin.jobfinder.data.entity.NetworkState;
import id.haerulmuttaqin.jobfinder.data.storage.GithubJobRepository;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
public class MainViewModel extends BaseViewModel<MainViewModel.Navigator> {
public MainViewModel(Context context, ConnectionServer connectionServer, GithubJobRepository repository) {
super(context, connectionServer, repository);
}
public LiveData<PagedList<GithubJob>> getPagedData(String keyword) {
return getRepository().getDataByPage(getConnectionServer(), getRepository(), keyword);
}
public LiveData<NetworkState> getNetworkState() {
return getRepository().getNetworkState();
}
public LiveData<List<GithubJob>> getLiveData() {
return getRepository().getLiveData();
}
public LiveData<List<GithubJob>> searchLiveData(String keyword) {
return getRepository().searchLiveData(keyword.toLowerCase());
}
public LiveData<List<GithubJob>> getLiveDataMarked() {
return getRepository().getLiveDataMarked();
}
public String formatDate(String date){
return Utils.dateToTimeFormat(date);
}
public void getJobFromServer() {
getNavigator().showProgress();
getConnectionServer().getJobList().enqueue(new Callback<List<GithubJob>>() {
@Override
public void onResponse(Call<List<GithubJob>> call, Response<List<GithubJob>> response) {
if (response.isSuccessful() && response.body().size() > 0) {
for (GithubJob item : response.body()) {
item.createdAt = Utils.dateFormatter(item.createdAt); // format date
getRepository().insert(item);
}
getNavigator().onGetResult(true, "Success");
}
getNavigator().hideProgress();
}
@Override
public void onFailure(Call<List<GithubJob>> call, Throwable t) {
t.getLocalizedMessage();
getNavigator().hideProgress();
getNavigator().onGetResult(false, Utils.errorMessageHandler(call, t));
}
});
}
public void searchJobFromServer(String keyword) {
getNavigator().showProgress();
getConnectionServer().searchJobList(keyword).enqueue(new Callback<List<GithubJob>>() {
@Override
public void onResponse(Call<List<GithubJob>> call, Response<List<GithubJob>> response) {
if (response.isSuccessful() && response.body().size() > 0) {
for (GithubJob item : response.body()) {
getRepository().insert(item);
}
getNavigator().onGetResult(true, "Success");
}
getNavigator().hideProgress();
}
@Override
public void onFailure(Call<List<GithubJob>> call, Throwable t) {
t.getLocalizedMessage();
getNavigator().hideProgress();
getNavigator().onGetResult(false, Utils.errorMessageHandler(call, t));
}
});
}
public void markJob(GithubJob githubJob) {
getRepository().updateMarkJob(githubJob);
getNavigator().onMark(githubJob.is_mark, githubJob.title);
}
public void itemClick(GithubJob githubJob) {
getNavigator().onItemClick(githubJob);
}
public static class ModelFactory implements ViewModelProvider.Factory {
private Context context;
private ConnectionServer server;
private GithubJobRepository repository;
public ModelFactory(Context context, ConnectionServer server, GithubJobRepository repository) {
this.context = context;
this.server = server;
this.repository = repository;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MainViewModel(context, server, repository);
}
}
public interface Navigator {
void showProgress();
void hideProgress();
void onGetResult(boolean status, String message);
void onMark(int mark, String title);
void onItemClick(GithubJob githubJob);
}
}
public class NetworkState {
public NetworkState(Status status, String msg) {
this.status = status;
this.msg = msg;
}
public enum Status {
FIRST_LOADING,
FIRST_FAILED,
RUNNING,
SUCCESS,
EMPTY,
FAILED
}
private final Status status;
private final String msg;
public static final NetworkState LOADED;
public static final NetworkState LOADING;
public static final NetworkState FIRST_LOADING;
public static final NetworkState FIRST_FAILED;
public static final NetworkState EMPTY;
static {
LOADED = new NetworkState(Status.SUCCESS,"Success");
LOADING = new NetworkState(Status.RUNNING,"Running");
FIRST_LOADING = new NetworkState(Status.FIRST_LOADING,"First Runnig");
FIRST_FAILED = new NetworkState(Status.FIRST_FAILED,"First Failed");
EMPTY = new NetworkState(Status.EMPTY, "Empty");
}
public Status getStatus() {
return status;
}
public String getMsg() {
return msg;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment