Split source class

This commit is contained in:
inorichi 2015-10-26 14:04:03 +01:00
parent 0ef610bb73
commit 8b0b174c93
18 changed files with 109 additions and 77 deletions

View file

@ -7,7 +7,6 @@ import com.pushtorefresh.storio.sqlite.StorIOSQLite;
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite; import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite;
import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResult; import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResult;
import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResults; import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResults;
import eu.kanade.mangafeed.util.PostResult;
import com.pushtorefresh.storio.sqlite.operations.put.PutResult; import com.pushtorefresh.storio.sqlite.operations.put.PutResult;
import com.pushtorefresh.storio.sqlite.operations.put.PutResults; import com.pushtorefresh.storio.sqlite.operations.put.PutResults;
@ -25,6 +24,7 @@ import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.data.models.MangaStorIOSQLiteDeleteResolver; import eu.kanade.mangafeed.data.models.MangaStorIOSQLiteDeleteResolver;
import eu.kanade.mangafeed.data.models.MangaStorIOSQLitePutResolver; import eu.kanade.mangafeed.data.models.MangaStorIOSQLitePutResolver;
import eu.kanade.mangafeed.data.resolvers.MangaWithUnreadGetResolver; import eu.kanade.mangafeed.data.resolvers.MangaWithUnreadGetResolver;
import eu.kanade.mangafeed.util.PostResult;
import rx.Observable; import rx.Observable;
public class DatabaseHelper implements MangaManager, ChapterManager { public class DatabaseHelper implements MangaManager, ChapterManager {

View file

@ -5,7 +5,7 @@ import android.content.SharedPreferences;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
public class PreferencesHelper { public class PreferencesHelper {

View file

@ -7,7 +7,7 @@ import java.util.List;
import eu.kanade.mangafeed.data.caches.CacheManager; import eu.kanade.mangafeed.data.caches.CacheManager;
import eu.kanade.mangafeed.sources.Batoto; import eu.kanade.mangafeed.sources.Batoto;
import eu.kanade.mangafeed.sources.MangaHere; import eu.kanade.mangafeed.sources.MangaHere;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
public class SourceManager { public class SourceManager {

View file

@ -2,7 +2,6 @@ package eu.kanade.mangafeed.data.managers;
import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResult; import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResult;
import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResults; import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResults;
import eu.kanade.mangafeed.util.PostResult;
import com.pushtorefresh.storio.sqlite.operations.put.PutResult; import com.pushtorefresh.storio.sqlite.operations.put.PutResult;
import com.pushtorefresh.storio.sqlite.operations.put.PutResults; import com.pushtorefresh.storio.sqlite.operations.put.PutResults;
@ -10,6 +9,7 @@ import java.util.List;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.util.PostResult;
import rx.Observable; import rx.Observable;
public interface ChapterManager { public interface ChapterManager {

View file

@ -4,7 +4,6 @@ import com.pushtorefresh.storio.sqlite.StorIOSQLite;
import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResult; import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResult;
import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResults; import com.pushtorefresh.storio.sqlite.operations.delete.DeleteResults;
import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects; import com.pushtorefresh.storio.sqlite.operations.get.PreparedGetListOfObjects;
import eu.kanade.mangafeed.util.PostResult;
import com.pushtorefresh.storio.sqlite.operations.put.PutResult; import com.pushtorefresh.storio.sqlite.operations.put.PutResult;
import com.pushtorefresh.storio.sqlite.operations.put.PutResults; import com.pushtorefresh.storio.sqlite.operations.put.PutResults;
import com.pushtorefresh.storio.sqlite.queries.Query; import com.pushtorefresh.storio.sqlite.queries.Query;
@ -14,6 +13,7 @@ import java.util.List;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.data.tables.ChaptersTable; import eu.kanade.mangafeed.data.tables.ChaptersTable;
import eu.kanade.mangafeed.util.PostResult;
import rx.Observable; import rx.Observable;
public class ChapterManagerImpl extends BaseManager implements ChapterManager { public class ChapterManagerImpl extends BaseManager implements ChapterManager {

View file

@ -1,7 +1,7 @@
package eu.kanade.mangafeed.events; package eu.kanade.mangafeed.events;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
public class SourceChapterEvent { public class SourceChapterEvent {

View file

@ -13,7 +13,7 @@ import javax.inject.Inject;
import eu.kanade.mangafeed.data.helpers.DatabaseHelper; import eu.kanade.mangafeed.data.helpers.DatabaseHelper;
import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.data.helpers.SourceManager;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
import eu.kanade.mangafeed.ui.fragment.CatalogueFragment; import eu.kanade.mangafeed.ui.fragment.CatalogueFragment;
import eu.kanade.mangafeed.util.PageBundle; import eu.kanade.mangafeed.util.PageBundle;
import eu.kanade.mangafeed.util.RxPager; import eu.kanade.mangafeed.util.RxPager;

View file

@ -2,8 +2,6 @@ package eu.kanade.mangafeed.presenter;
import android.os.Bundle; import android.os.Bundle;
import eu.kanade.mangafeed.util.PostResult;
import java.util.List; import java.util.List;
import javax.inject.Inject; import javax.inject.Inject;
@ -15,9 +13,10 @@ import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.events.ChapterCountEvent; import eu.kanade.mangafeed.events.ChapterCountEvent;
import eu.kanade.mangafeed.events.SourceChapterEvent; import eu.kanade.mangafeed.events.SourceChapterEvent;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment; import eu.kanade.mangafeed.ui.fragment.MangaChaptersFragment;
import eu.kanade.mangafeed.util.EventBusHook; import eu.kanade.mangafeed.util.EventBusHook;
import eu.kanade.mangafeed.util.PostResult;
import rx.Observable; import rx.Observable;
import rx.android.schedulers.AndroidSchedulers; import rx.android.schedulers.AndroidSchedulers;
import rx.schedulers.Schedulers; import rx.schedulers.Schedulers;

View file

@ -16,7 +16,7 @@ import eu.kanade.mangafeed.data.helpers.PreferencesHelper;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Page; import eu.kanade.mangafeed.data.models.Page;
import eu.kanade.mangafeed.events.SourceChapterEvent; import eu.kanade.mangafeed.events.SourceChapterEvent;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
import eu.kanade.mangafeed.ui.activity.ReaderActivity; import eu.kanade.mangafeed.ui.activity.ReaderActivity;
import eu.kanade.mangafeed.util.EventBusHook; import eu.kanade.mangafeed.util.EventBusHook;
import icepick.State; import icepick.State;

View file

@ -19,6 +19,7 @@ import eu.kanade.mangafeed.data.helpers.NetworkHelper;
import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.data.helpers.SourceManager;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.sources.base.Source;
import rx.Observable; import rx.Observable;
public class Batoto extends Source { public class Batoto extends Source {
@ -114,19 +115,19 @@ public class Batoto extends Source {
} }
@Override @Override
protected String getMangaUrl(String defaultMangaUrl) { protected String overrideMangaUrl(String defaultMangaUrl) {
String mangaId = defaultMangaUrl.substring(defaultMangaUrl.lastIndexOf("r") + 1); String mangaId = defaultMangaUrl.substring(defaultMangaUrl.lastIndexOf("r") + 1);
return "http://bato.to/comic_pop?id=" + mangaId; return "http://bato.to/comic_pop?id=" + mangaId;
} }
@Override @Override
protected String getChapterPageUrl(String defaultPageUrl) { protected String overrideChapterPageUrl(String defaultPageUrl) {
String id = defaultPageUrl.substring(defaultPageUrl.indexOf("#") + 1); String id = defaultPageUrl.substring(defaultPageUrl.indexOf("#") + 1);
return INITIAL_PAGE_URL + "id=" + id + "&p=1"; return INITIAL_PAGE_URL + "id=" + id + "&p=1";
} }
@Override @Override
protected String getRemainingPagesUrl(String defaultPageUrl) { protected String overrideRemainingPagesUrl(String defaultPageUrl) {
int start = defaultPageUrl.indexOf("#") + 1; int start = defaultPageUrl.indexOf("#") + 1;
int end = defaultPageUrl.indexOf("_", start); int end = defaultPageUrl.indexOf("_", start);
String id = defaultPageUrl.substring(start, end); String id = defaultPageUrl.substring(start, end);

View file

@ -18,6 +18,7 @@ import eu.kanade.mangafeed.data.helpers.NetworkHelper;
import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.data.helpers.SourceManager;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.sources.base.Source;
import rx.Observable; import rx.Observable;
public class MangaHere extends Source { public class MangaHere extends Source {
@ -37,6 +38,7 @@ public class MangaHere extends Source {
return NAME; return NAME;
} }
@Override
public int getSourceId() { public int getSourceId() {
return SourceManager.MANGAHERE; return SourceManager.MANGAHERE;
} }
@ -51,6 +53,12 @@ public class MangaHere extends Source {
return INITIAL_SEARCH_URL + "name=" + query + "&page=" + page; return INITIAL_SEARCH_URL + "name=" + query + "&page=" + page;
} }
@Override
public boolean isLoginRequired() {
return false;
}
public Observable<List<String>> getGenres() { public Observable<List<String>> getGenres() {
List<String> genres = new ArrayList<>(30); List<String> genres = new ArrayList<>(30);

View file

@ -0,0 +1,76 @@
package eu.kanade.mangafeed.sources.base;
import com.squareup.okhttp.Headers;
import java.util.List;
import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga;
public abstract class BaseSource {
// Name of the source to display
public abstract String getName();
// Id of the source (must be declared and obtained from SourceManager to avoid conflicts)
public abstract int getSourceId();
// True if the source requires a login
public abstract boolean isLoginRequired();
// Given a page number, it should return the URL of the page where the manga list is found
protected abstract String getUrlFromPageNumber(int page);
// From the URL obtained before, this method must return a list of mangas
protected abstract List<Manga> parsePopularMangasFromHtml(String unparsedHtml);
// Given a query and a page number, return the URL of the results
protected abstract String getSearchUrl(String query, int page);
// From the URL obtained before, this method must return a list of mangas
protected abstract List<Manga> parseSearchFromHtml(String unparsedHtml);
// Given the URL of a manga and the result of the request, return the details of the manga
protected abstract Manga parseHtmlToManga(String mangaUrl, String unparsedHtml);
// Given the result of the request to mangas' chapters, return a list of chapters
protected abstract List<Chapter> parseHtmlToChapters(String unparsedHtml);
// Given the result of the request to a chapter, return the list of URLs of the chapter
protected abstract List<String> parseHtmlToPageUrls(String unparsedHtml);
// Given the result of the request to a chapter's page, return the URL of the image of the page
protected abstract String parseHtmlToImageUrl(String unparsedHtml);
// Default fields, they can be overriden by sources' implementation
// Get the URL to the details of a manga, useful if the source provides some kind of API or fast calls
protected String overrideMangaUrl(String defaultMangaUrl) {
return defaultMangaUrl;
}
// Get the URL of the first page that contains a source image and the page list
protected String overrideChapterPageUrl(String defaultPageUrl) {
return defaultPageUrl;
}
// Get the URL of the remaining pages that contains source images
protected String overrideRemainingPagesUrl(String defaultPageUrl) {
return defaultPageUrl;
}
// Default headers, it can be overriden by children or just add new keys
protected Headers.Builder headersBuilder() {
Headers.Builder builder = new Headers.Builder();
builder.add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)");
return builder;
}
// Number of images to download at the same time. 3 by default
protected int overrideNumberOfConcurrentPageDownloads() {
return 3;
}
}

View file

@ -1,4 +1,4 @@
package eu.kanade.mangafeed.sources; package eu.kanade.mangafeed.sources.base;
import com.squareup.okhttp.Headers; import com.squareup.okhttp.Headers;
@ -14,59 +14,7 @@ import eu.kanade.mangafeed.data.models.Page;
import rx.Observable; import rx.Observable;
import rx.schedulers.Schedulers; import rx.schedulers.Schedulers;
public abstract class Source { public abstract class Source extends BaseSource {
// Methods to implement or optionally override
// Name of the source to display
public abstract String getName();
// Id of the source (must be declared and obtained from SourceManager to avoid conflicts)
public abstract int getSourceId();
protected abstract String getUrlFromPageNumber(int page);
protected abstract String getSearchUrl(String query, int page);
protected abstract List<Manga> parsePopularMangasFromHtml(String unparsedHtml);
protected abstract List<Manga> parseSearchFromHtml(String unparsedHtml);
protected abstract Manga parseHtmlToManga(String mangaUrl, String unparsedHtml);
protected abstract List<Chapter> parseHtmlToChapters(String unparsedHtml);
protected abstract List<String> parseHtmlToPageUrls(String unparsedHtml);
protected abstract String parseHtmlToImageUrl(String unparsedHtml);
// True if the source requires a login
public boolean isLoginRequired() {
return false;
}
// Get the URL to the details of a manga, useful if the source provides some kind of API or fast calls
protected String getMangaUrl(String defaultMangaUrl) {
return defaultMangaUrl;
}
// Get the URL of the first page that contains a source image and the page list
protected String getChapterPageUrl(String defaultPageUrl) {
return defaultPageUrl;
}
// Get the URL of the remaining pages that contains source images
protected String getRemainingPagesUrl(String defaultPageUrl) {
return defaultPageUrl;
}
// Default headers, it can be overriden by children or just add new keys
protected Headers.Builder headersBuilder() {
Headers.Builder builder = new Headers.Builder();
builder.add("User-Agent", "Mozilla/5.0 (Windows NT 6.3; WOW64)");
return builder;
}
// Number of images to download at the same time
protected int getNumberOfConcurrentPageDownloads() {
return 3;
}
// ***** Source class implementation *****
protected NetworkHelper mNetworkService; protected NetworkHelper mNetworkService;
protected CacheManager mCacheManager; protected CacheManager mCacheManager;
@ -96,7 +44,7 @@ public abstract class Source {
// Get manga details from the source // Get manga details from the source
public Observable<Manga> pullMangaFromNetwork(final String mangaUrl) { public Observable<Manga> pullMangaFromNetwork(final String mangaUrl) {
return mNetworkService return mNetworkService
.getStringResponse(getMangaUrl(mangaUrl), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) .getStringResponse(overrideMangaUrl(mangaUrl), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders)
.flatMap(unparsedHtml -> Observable.just(parseHtmlToManga(mangaUrl, unparsedHtml))); .flatMap(unparsedHtml -> Observable.just(parseHtmlToManga(mangaUrl, unparsedHtml)));
} }
@ -112,7 +60,7 @@ public abstract class Source {
return mCacheManager.getPageUrlsFromDiskCache(chapterUrl) return mCacheManager.getPageUrlsFromDiskCache(chapterUrl)
.onErrorResumeNext(throwable -> { .onErrorResumeNext(throwable -> {
return mNetworkService return mNetworkService
.getStringResponse(getChapterPageUrl(chapterUrl), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) .getStringResponse(overrideChapterPageUrl(chapterUrl), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders)
.flatMap(unparsedHtml -> { .flatMap(unparsedHtml -> {
List<String> pageUrls = parseHtmlToPageUrls(unparsedHtml); List<String> pageUrls = parseHtmlToPageUrls(unparsedHtml);
return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml)); return Observable.just(getFirstImageFromPageUrls(pageUrls, unparsedHtml));
@ -126,7 +74,7 @@ public abstract class Source {
public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) { public Observable<Page> getRemainingImageUrlsFromPageList(final List<Page> pages) {
return Observable.from(pages) return Observable.from(pages)
.filter(page -> page.getImageUrl() == null) .filter(page -> page.getImageUrl() == null)
.window(getNumberOfConcurrentPageDownloads()) .window(overrideNumberOfConcurrentPageDownloads())
.concatMap(batchedPages -> .concatMap(batchedPages ->
batchedPages.concatMap(this::getImageUrlFromPage) batchedPages.concatMap(this::getImageUrlFromPage)
); );
@ -134,7 +82,7 @@ public abstract class Source {
private Observable<Page> getImageUrlFromPage(final Page page) { private Observable<Page> getImageUrlFromPage(final Page page) {
return mNetworkService return mNetworkService
.getStringResponse(getRemainingPagesUrl(page.getUrl()), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders) .getStringResponse(overrideRemainingPagesUrl(page.getUrl()), mNetworkService.NULL_CACHE_CONTROL, mRequestHeaders)
.flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml))) .flatMap(unparsedHtml -> Observable.just(parseHtmlToImageUrl(unparsedHtml)))
.flatMap(imageUrl -> { .flatMap(imageUrl -> {
page.setImageUrl(imageUrl); page.setImageUrl(imageUrl);

View file

@ -4,7 +4,7 @@ import android.view.View;
import android.widget.TextView; import android.widget.TextView;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
import uk.co.ribot.easyadapter.ItemViewHolder; import uk.co.ribot.easyadapter.ItemViewHolder;
import uk.co.ribot.easyadapter.PositionInfo; import uk.co.ribot.easyadapter.PositionInfo;
import uk.co.ribot.easyadapter.annotations.LayoutId; import uk.co.ribot.easyadapter.annotations.LayoutId;

View file

@ -22,7 +22,7 @@ import eu.kanade.mangafeed.App;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.data.helpers.PreferencesHelper; import eu.kanade.mangafeed.data.helpers.PreferencesHelper;
import eu.kanade.mangafeed.data.helpers.SourceManager; import eu.kanade.mangafeed.data.helpers.SourceManager;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
import eu.kanade.mangafeed.ui.activity.base.BaseActivity; import eu.kanade.mangafeed.ui.activity.base.BaseActivity;
import rx.Observable; import rx.Observable;

View file

@ -13,7 +13,7 @@ import butterknife.ButterKnife;
import butterknife.OnItemClick; import butterknife.OnItemClick;
import eu.kanade.mangafeed.R; import eu.kanade.mangafeed.R;
import eu.kanade.mangafeed.presenter.SourcePresenter; import eu.kanade.mangafeed.presenter.SourcePresenter;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
import eu.kanade.mangafeed.ui.activity.MainActivity; import eu.kanade.mangafeed.ui.activity.MainActivity;
import eu.kanade.mangafeed.ui.adapter.SourceHolder; import eu.kanade.mangafeed.ui.adapter.SourceHolder;
import eu.kanade.mangafeed.ui.fragment.base.BaseRxFragment; import eu.kanade.mangafeed.ui.fragment.base.BaseRxFragment;

View file

@ -18,7 +18,7 @@ import eu.kanade.mangafeed.data.helpers.NetworkHelper;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.sources.Batoto; import eu.kanade.mangafeed.sources.Batoto;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) @Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)

View file

@ -18,7 +18,7 @@ import eu.kanade.mangafeed.data.helpers.NetworkHelper;
import eu.kanade.mangafeed.data.models.Chapter; import eu.kanade.mangafeed.data.models.Chapter;
import eu.kanade.mangafeed.data.models.Manga; import eu.kanade.mangafeed.data.models.Manga;
import eu.kanade.mangafeed.sources.MangaHere; import eu.kanade.mangafeed.sources.MangaHere;
import eu.kanade.mangafeed.sources.Source; import eu.kanade.mangafeed.sources.base.Source;
@Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP) @Config(constants = BuildConfig.class, sdk = Build.VERSION_CODES.LOLLIPOP)
@RunWith(RobolectricGradleTestRunner.class) @RunWith(RobolectricGradleTestRunner.class)