티스토리 뷰
mvvm GitHubService
그래들 설정
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:27.1.1'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
implementation 'com.android.support:design:27.1.1'
implementation 'com.android.support:cardview-v7:27.1.1'
// http image
implementation 'com.github.bumptech.glide:glide:3.6.1'
// API액세스
implementation 'com.squareup.retrofit2:retrofit:2.0.0'
implementation 'com.squareup.retrofit2:converter-gson:2.0.0'
implementation 'com.squareup.retrofit2:adapter-rxjava:2.0.0'
implementation 'com.squareup.okhttp3:logging-interceptor:3.0.1'
// Rx
implementation 'io.reactivex:rxjava:1.0.17'
implementation 'io.reactivex:rxandroid:1.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
}
매니페스트 설정 : 어플리케이션 클래스 등록
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.neofuture.myapplication">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".NewGitHubReposApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".view.RepositoryListViewActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
application class
public class NewGitHubReposApplication extends Application {
private Retrofit retrofit;
private GitHubService gitHubService;
@Override
public void onCreate() {
super.onCreate();
// 어느 Activity에서라도 API를 이용할 수 있게 이 클래스에서 API를 이용한다
setupAPIClient();
}
private void setupAPIClient() {
HttpLoggingInterceptor logging = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
@Override
public void log(String message) {
Log.d("API LOG", message);
}
});
logging.setLevel(HttpLoggingInterceptor.Level.BASIC);
OkHttpClient client = new OkHttpClient.Builder().addInterceptor(logging).build();
retrofit = new Retrofit.Builder()
.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.baseUrl("https://api.github.com")
.addConverterFactory(GsonConverterFactory.create())
.client(client)
.build();
gitHubService = retrofit.create(GitHubService.class);
}
public GitHubService getGitHubService() {
return gitHubService;
}
}
retrofit interface
/**
* Retrofit로 GitHub의 API를 이용하기 위한 클래스
*/
public interface GitHubService {
/**
* GitHub의 리포지터리 검색결과를 가져온다
* https://developer.github.com/v3/search/
* @param query GitHub의 API로 검색하는 내용
* @return API 액세스 결과 취득 후의 콜백으로서 SearchResponse를 얻을 수 있는 RxJavaのObservable로 반환한다
*/
@GET("search/repositories?sort=stars&order=desc")
Observable<Repositories> listRepos(@Query("q") String query);
/**
* 리포지토리 상세 내역을 가져온다
* https://developer.github.com/v3/repos/#get
* @return API 액세스 결과 취득 후의 콜백으로서 RepositoryItem을 얻을 수 있는 RxJavaのObservable로 반환한다
*/
@GET("repos/{repoOwner}/{repoName}")
Observable<RepositoryItem> detailRepo(@Path(value = "repoOwner") String owner, @Path(value = "repoName") String repoName);
/**
* API 액세스 결과가 이 클래스에 들어온다
* GitHub의 리포지터리 목록이 들어있다
* @see GitHubService#listRepos(String)
*/
public static class Repositories {
public final List<RepositoryItem> items;
public Repositories(List<RepositoryItem> items) {
this.items = items;
}
}
/**
* API 액세스 결과가 이 클래스에 들어온다
* GitHub의 리포지터리 데이터가 들어있다
* @see GitHubService#detailRepo(String, String)
*/
public static class RepositoryItem {
public final String description;
public final Owner owner;
public final String language;
public final String name;
public final String stargazers_count;
public final String forks_count;
public final String full_name;
public final String html_url;
public RepositoryItem(String description, Owner owner, String language, String name, String stargazers_count, String forks_count, String full_name, String html_url) {
this.description = description;
this.owner = owner;
this.language = language;
this.name = name;
this.stargazers_count = stargazers_count;
this.forks_count = forks_count;
this.full_name = full_name;
this.html_url = html_url;
}
}
/**
* GitHub의 리포지터리에 대한 소유자 데이터가 들어 있다
* @see GitHubService#detailRepo(String, String)
*/
public static class Owner {
public final String received_events_url;
public final String organizations_url;
public final String avatar_url;
public final String gravatar_id;
public final String gists_url;
public final String starred_url;
public final String site_admin;
public final String type;
public final String url;
public final String id;
public final String html_url;
public final String following_url;
public final String events_url;
public final String login;
public final String subscriptions_url;
public final String repos_url;
public final String followers_url;
public Owner(String received_events_url, String organizations_url, String avatar_url, String gravatar_id, String gists_url, String starred_url, String site_admin, String type, String url, String id, String html_url, String following_url, String events_url, String login, String subscriptions_url, String repos_url, String followers_url) {
this.received_events_url = received_events_url;
this.organizations_url = organizations_url;
this.avatar_url = avatar_url;
this.gravatar_id = gravatar_id;
this.gists_url = gists_url;
this.starred_url = starred_url;
this.site_admin = site_admin;
this.type = type;
this.url = url;
this.id = id;
this.html_url = html_url;
this.following_url = following_url;
this.events_url = events_url;
this.login = login;
this.subscriptions_url = subscriptions_url;
this.repos_url = repos_url;
this.followers_url = followers_url;
}
}
}
xml 생성
activity_repository.xml
<?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>
<!-- RepositoryListViewModel에 바인딩한다 -->
<variable
name="viewModel"
type="com.github.advanced_android.newgithubrepo.viewmodel.RepositoryListViewModel"/>
</data>
<android.support.design.widget.CoordinatorLayout
android:id="@+id/coordinator_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
tools:context=".view.RepositoryListActivity">
<android.support.design.widget.AppBarLayout
android:id="@+id/app_bar"
android:layout_width="match_parent"
android:layout_height="@dimen/app_bar_height"
android:fitsSystemWindows="true"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.design.widget.CollapsingToolbarLayout
android:id="@+id/toolbar_layout"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:fitsSystemWindows="true"
app:contentScrim="?attr/colorPrimary"
app:layout_scrollFlags="scroll|exitUntilCollapsed">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
app:layout_collapseMode="pin"
app:popupTheme="@style/AppTheme.PopupOverlay"/>
</android.support.design.widget.CollapsingToolbarLayout>
<!-- ViewModelのonLanguageSpinnerItemSelected()를 onItemSelectedListener로 이용한다 -->
<Spinner
android:id="@+id/language_spinner"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:fitsSystemWindows="true"
android:onItemSelected="@{viewModel::onLanguageSpinnerItemSelected}"
/>
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_repository_list"/>
<!-- ViewModelのprogressBarVisibility를 View의 visibility(View 표시 여부)로 이용한다 -->
<ProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="@{viewModel.progressBarVisibility}"
/>
</android.support.design.widget.CoordinatorLayout>
</layout>
/**
* 리포지토리 목록을 표시하는 액티비티
* MVVM의 뷰 역할을 한다
*/
public class RepositoryListActivity extends AppCompatActivity implements RepositoryListViewContract {
private Spinner languageSpinner;
private CoordinatorLayout coordinatorLayout;
private RepositoryAdapter repositoryAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ActivityRepositoryListBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_repository_list);
final GitHubService gitHubService = ((NewGitHubReposApplication) getApplication()).getGitHubService();
binding.setViewModel(new RepositoryListViewModel((RepositoryListViewContract) this, gitHubService));
// 뷰를 셋업
setupViews();
}
/**
* 목록 등 화면 요소를 만든다
*/
private void setupViews() {
// 툴바 설정
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
// Recycler View
RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_repos);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
repositoryAdapter = new RepositoryAdapter((Context) this, (RepositoryListViewContract) this);
recyclerView.setAdapter(repositoryAdapter);
// SnackBar 표시에서 이용한다
coordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator_layout);
// Spinner
languageSpinner = (Spinner) findViewById(R.id.language_spinner);
ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item);
adapter.addAll("java", "objective-c", "swift", "groovy", "python", "ruby", "c");
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
languageSpinner.setAdapter(adapter);
}
// =====RepositoryListViewContract 구현=====
// 여기서 Presenter로부터 지시를 받아 뷰의 변경 등을 한다
@Override
public void startDetailActivity(String full_name) {
DetailActivity.start(this, full_name);
}
@Override
public void showRepositories(GitHubService.Repositories repositories) {
repositoryAdapter.setItemsAndRefresh(repositories.items);
}
@Override
public void showError() {
Snackbar.make(coordinatorLayout, "읽을 수 없습니다", Snackbar.LENGTH_LONG)
.setAction("Action", null).show();
}
}
/**
* MVVM의 ViewModel 역할을 하는 클래스
*/
public class RepositoryListViewModel {
public final ObservableInt progressBarVisibility = new ObservableInt(View.VISIBLE);
private final RepositoryListViewContract repositoryListView;
private final GitHubService gitHubService;
public RepositoryListViewModel(RepositoryListViewContract repositoryListView, GitHubService gitHubService) {
this.repositoryListView = repositoryListView;
this.gitHubService = gitHubService;
}
public void onLanguageSpinnerItemSelected(AdapterView<?> parent, View view, int position, long id) {
// 스피너의 선택 내용이 바뀌면 호출된다
loadRepositories((String) parent.getItemAtPosition(position));
}
/**
* 지난 일주일간 만들어진 라이브러리를 인기순으로 가져온다
*/
private void loadRepositories(String langugae) {
// 로딩 중이므로 진행바를 표시한다
progressBarVisibility.set(View.VISIBLE);
// 일주일 전 날짜 문자열 지금이 2016-10-27이라면 2016-10-20이라는 문자열을 얻는다
final Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -7);
String text = DateFormat.format("yyyy-MM-dd", calendar).toString();
// Retrofit을 이용해 서버에 액세스한다
// 지난 일주일간 만들어졌고 언어가 language인 것을 쿼리로 전달한다
Observable<GitHubService.Repositories> observable = gitHubService.listRepos("language:" + langugae + " " + "created:>" + text);
// 입출력(IO)용 스레드로 통신하고, 메인스레드로 결과를 받도록 한다
observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(new Subscriber<GitHubService.Repositories>() {
@Override
public void onNext(GitHubService.Repositories repositories) {
// 로딩이 끝났으므로 진행바를 표시하지 않는다
progressBarVisibility.set(View.GONE);
// 가져온 아이템을 표시하고자, RecyclerView에 아이템을 설정해 갱신한다
repositoryListView.showRepositories(repositories);
}
@Override
public void onError(Throwable e) {
// 통신에 실패하면 호출된다
// 여기서는 스낵바를 표시한다(아래에 표시되는 바)
repositoryListView.showError();
}
@Override
public void onCompleted() {
// 아무것도 하지 않는다
}
});
}
}
/**
* 리포지터리 목록 화면이 가진 Contract(계약)를 정의해두는 인터페이스
* <p>
* ViewModel이 직접 Activity를 참조하지 않도록 인터페이스로 명확히 나눈다.
*/
public interface RepositoryListViewContract {
void showRepositories(GitHubService.Repositories repositories);
void showError();
void startDetailActivity(String fullRepositoryName);
}
content_repository_list.xml
<?xml version="1.0" encoding="utf-8"?>
<merge xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:showIn="@layout/activity_repository_list">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_repos"
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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".view.RepositoryListActivity"
tools:listitem="@layout/repo_item"
tools:showIn="@layout/activity_repository_list">
</android.support.v7.widget.RecyclerView>
</merge>
/**
* RecyclerView에서 리포지토리 목록을 표시하기 위한 Adapter 클래스
* 이 클래스에 의해 RecyclerView 아이템의 뷰를 생성하고 뷰에 데이터를 넣는다
*/
public class RepositoryAdapter extends RecyclerView.Adapter<RepositoryAdapter.RepoViewHolder> {
private final RepositoryListViewContract view;
private final Context context;
private List<GitHubService.RepositoryItem> items;
public RepositoryAdapter(Context context, RepositoryListViewContract view) {
this.context = context;
this.view = view;
}
/**
* 리포지토리의 데이터를 설정해서 갱신한다
* @param items
*/
public void setItemsAndRefresh(List<GitHubService.RepositoryItem> items) {
this.items = items;
notifyDataSetChanged();
}
public GitHubService.RepositoryItem getItemAt(int position) {
return items.get(position);
}
/**
* RecyclerView의 아이템의 뷰 작성과 뷰를 보존할 ViewHolder를 생성
*/
@Override
public RepoViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
RepoItemBinding binding = DataBindingUtil.inflate(LayoutInflater.from(context), R.layout.repo_item, parent, false);
binding.setViewModel(new RepositoryItemViewModel(view));
return new RepoViewHolder(binding.getRoot(), binding.getViewModel());
}
/**
* onCreateViewHolder에서 만든 ViewHolder의 뷰에
* setItemsAndRefresh(items)에서 설정된 데이터를 넣는다
*/
@Override
public void onBindViewHolder(final RepoViewHolder holder, final int position) {
final GitHubService.RepositoryItem item = getItemAt(position);
holder.loadItem(item);
}
@Override
public int getItemCount() {
if (items == null) {
return 0;
}
return items.size();
}
/**
* 뷰를 보존해 둘 클래스
* 여기서는 ViewModel을 가진다
*/
static class RepoViewHolder extends RecyclerView.ViewHolder {
private final RepositoryItemViewModel viewModel;
public RepoViewHolder(View itemView, RepositoryItemViewModel viewModel) {
super(itemView);
this.viewModel = viewModel;
}
public void loadItem(GitHubService.RepositoryItem item) {
viewModel.loadItem(item);
}
}
}
repo_item.xml
<?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:bind="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="viewModel"
type="com.github.advanced_android.newgithubrepo.viewmodel.RepositoryItemViewModel" />
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:clickable="true"
android:foreground="?selectableItemBackground"
android:onClick="@{viewModel::onItemClick}"
android:orientation="vertical"
app:cardUseCompatPadding="true"
tools:showIn="@layout/activity_repository_list">
<RelativeLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="8dp">
<ImageView
android:id="@+id/repo_image"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_margin="8dp"
bind:imageUrl="@{viewModel.repoImageUrl}" />
<TextView
android:id="@+id/repo_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@+id/repo_star_image"
android:layout_toRightOf="@id/repo_image"
android:text="@{viewModel.repoName}"
android:textAppearance="@android:style/TextAppearance.Large"
tools:text="title" />
<TextView
android:id="@+id/repo_detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/repo_name"
android:layout_marginBottom="4dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="4dp"
android:layout_toRightOf="@id/repo_image"
android:maxLines="20"
android:text="@{viewModel.repoDetail}"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents" />
<TextView
android:id="@+id/repo_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_margin="8dp"
android:text="@{viewModel.repoStar}"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents" />
<ImageView
android:id="@id/repo_star_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/repo_star"
android:src="@drawable/ic_star_black_18dp" />
</RelativeLayout>
</android.support.v7.widget.CardView>
</layout>
상세 화면
/**
* 상세 화면을 표시하는 액티비티
*/
public class DetailActivity extends AppCompatActivity implements DetailViewContract {
private static final String EXTRA_FULL_REPOSITORY_NAME = "EXTRA_FULL_REPOSITORY_NAME";
private String fullRepoName;
/**
* DetailActivity를 시작하는 메소드
* @param fullRepositoryName 표시하고 싶은 리포지토리 이름(google/iosched 등)
*/
public static void start(Context context, String fullRepositoryName) {
final Intent intent = new Intent(context, DetailActivity.class);
intent.putExtra(EXTRA_FULL_REPOSITORY_NAME, fullRepositoryName);
context.startActivity(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final ActivityDetailBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_detail);
final GitHubService gitHubService = ((NewGitHubReposApplication) getApplication()).getGitHubService();
final DetailViewModel detailViewModel = new DetailViewModel((DetailViewContract) this, gitHubService);
binding.setViewModel(detailViewModel);
final Intent intent = getIntent();
fullRepoName = intent.getStringExtra(EXTRA_FULL_REPOSITORY_NAME);
detailViewModel.loadRepositories();
}
@Override
public String getFullRepositoryName() {
return fullRepoName;
}
/**
* @throws Exception
*/
@Override
public void startBrowser(String url) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
@Override
public void showError(String message) {
Snackbar.make(findViewById(android.R.id.content), message, Snackbar.LENGTH_LONG)
.show();
}
}
<?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>
<variable
name="viewModel"
type="com.github.advanced_android.newgithubrepo.viewmodel.DetailViewModel"/>
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".view.DetailActivity">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="8dp">
<ImageView
android:id="@+id/owner_image"
android:layout_width="72dp"
android:layout_height="72dp"
android:layout_centerHorizontal="true"
android:layout_margin="8dp"
android:onClick="@{viewModel::onTitleClick}"
app:imageUrl="@{viewModel.repoImageUrl}"
tools:src="@android:drawable/sym_def_app_icon"/>
<TextView
android:id="@+id/fullname"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentLeft="true"
android:layout_below="@id/owner_image"
android:layout_marginBottom="32dp"
android:layout_marginLeft="8dp"
android:layout_marginRight="8dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@+id/repo_star_image"
android:onClick="@{viewModel::onTitleClick}"
android:text="@{viewModel.repoFullName}"
android:textAppearance="@android:style/TextAppearance.Large"
tools:text="owner/reponame"/>
<TextView
android:id="@+id/detail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/fullname"
android:layout_margin="8dp"
android:text="@{viewModel.repoDetail}"
android:textAppearance="@android:style/TextAppearance"
tools:text="this is detail text. aaaaaaaaaaaaaaaaaaaaaaaaa"/>
<TextView
android:id="@+id/repo_star"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/owner_image"
android:layout_margin="8dp"
android:text="@{viewModel.repoStar}"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents"/>
<ImageView
android:id="@id/repo_star_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/owner_image"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/repo_star"
android:src="@drawable/ic_star_black_18dp"/>
<TextView
android:id="@+id/repo_fork"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentRight="true"
android:layout_below="@id/repo_star"
android:layout_margin="8dp"
android:text="@{viewModel.repoFork}"
android:textAppearance="@android:style/TextAppearance"
tools:text="contents"/>
<ImageView
android:id="@+id/repo_fork_image"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/repo_star"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginTop="8dp"
android:layout_toLeftOf="@id/repo_star"
android:src="@drawable/ic_call_split_black_18dp"/>
</RelativeLayout>
</android.support.v7.widget.CardView>
</layout>
public class DetailViewModel {
final DetailViewContract detailView;
private final GitHubService gitHubService;
public ObservableField<String> repoFullName = new ObservableField<>();
public ObservableField<String> repoDetail = new ObservableField<>();
public ObservableField<String> repoStar = new ObservableField<>();
public ObservableField<String> repoFork = new ObservableField<>();
public ObservableField<String> repoImageUrl = new ObservableField<>();
private GitHubService.RepositoryItem repositoryItem;
public DetailViewModel(DetailViewContract detailView, GitHubService gitHubService) {
this.detailView = detailView;
this.gitHubService = gitHubService;
}
public void prepare() {
loadRepositories();
}
/**
* 하나의 리포지터리에 대한 정보를 가져온다
* 기본적으로 API 액세스 방법에 대해서는 RepositoryListActivity#loadRepositories(String)와 같다
*/
public void loadRepositories() {
String fullRepoName = detailView.getFullRepositoryName();
// 리포지터리 이름을 /로 나눈다
final String[] repoData = fullRepoName.split("/");
final String owner = repoData[0];
final String repoName = repoData[1];
gitHubService
.detailRepo(owner, repoName)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<GitHubService.RepositoryItem>() {
@Override
public void onCompleted() {
// 아무것도 하지 않는다
}
@Override
public void onError(Throwable e) {
detailView.showError("error。");
}
@Override
public void onNext(GitHubService.RepositoryItem repositoryItem) {
loadRepositoryItem(repositoryItem);
}
});
}
private void loadRepositoryItem(GitHubService.RepositoryItem repositoryItem) {
this.repositoryItem = repositoryItem;
repoFullName.set(repositoryItem.full_name);
repoDetail.set(repositoryItem.description);
repoStar.set(repositoryItem.stargazers_count);
repoFork.set(repositoryItem.forks_count);
repoImageUrl.set(repositoryItem.owner.avatar_url);
}
public void onTitleClick(View v) {
try {
detailView.startBrowser(repositoryItem.html_url);
} catch (Exception e) {
detailView.showError("링크를 열 수 없습니다.");
}
}
}
/**
* 상세 화면의 뷰가 가진 Contract(계약)을 정의해 두는 인터페이스
* <p>
* ViewModel이 직접 Activity를 참조하지 않도록 인터페이스로 명확히 나눈다
*/
public interface DetailViewContract {
String getFullRepositoryName();
void startBrowser(String url);
void showError(String message);
}
public class BindingAdapters {
@BindingAdapter({"imageUrl"})
public static void loadImage(final ImageView imageView, final String imageUrl) {
// 이미지는 Glide라는 라이브러리를 사용해 데이터를 설정한다
Glide.with(imageView.getContext())
.load(imageUrl)
.asBitmap().centerCrop().into(new BitmapImageViewTarget(imageView) {
@Override
protected void setResource(Bitmap resource) {
// 이미지를 동그랗게 오려낸다
RoundedBitmapDrawable circularBitmapDrawable = RoundedBitmapDrawableFactory.create(imageView.getResources(), resource);
circularBitmapDrawable.setCircular(true);
imageView.setImageDrawable(circularBitmapDrawable);
}
});
}
}
'android' 카테고리의 다른 글
15. NavigationView 사용하기 (0) | 2018.08.10 |
---|---|
14. BIM 계산 (Service, LocalBroadcastManager 이용방법) (0) | 2018.08.03 |
12.GitHubService Ex (1) | 2018.07.06 |
11.ContentProbider을 알자 (0) | 2018.05.26 |
10.IntentService를 활용하자 (0) | 2018.05.26 |