Вот надумал написать обзор библиотек с помощью которых легко и удобно писать приложения под Android.
Список вырисовывается такой:
Если заинтересованны прошу под кат.
Начать надо с того,что языки не мой конек, заранее прощу прощения зачем столько библиотек, чем это обусловлено и зачем надо.
Естественно, все продиктовано архитектурой приложения. Исходя из моего опыта, почти всегда, надо иметь следующую структуру приложения:
Как видно, приложение делится на 3 слоя — «мордочка», хранилище данных и сервис для асинхронных команд, где почти всегда скрыта логика.
Я не хочу разводить холивар и спорить об архитектуре, я просто описываю то как делаю это я и как делают мои коллеги.
Контент провайдер очень мощная штука с уведомлениями об изменениях данных и это все из коробки. Юзаем в основном как обвертку над базой. Способа как туда правильно впихнуть загрузку данных из инета не нашел.
Сервис асинхронных команд — из названия все ясно, выполняет действия асинхронно(в другом потоке). Практически все действия должны быть асинхронны — запись в базу, походы в инет, да и подсчеты.
Почему «асинхронных команд»? Тут тоже все просто — каждое действие — законченная команда. Которая знает с какими параметрами запуститься, что с ними делать и оповещением о своем завершении.
Вот тут evilduck уже все детально описал.
«Мордочка» — набор активити/фрагментов для отображения данных. Хочу заметить, что вся загрузка данных из хранилища должна быть асинхронная(ни каких походов в базу из UI даже за одним числом). И тут нам на помощь приходит лоадер менеджер — тоже фича из коробки. Стоит смотреть в сторону CursorLoader
Наверное, каждый программист делал свой велосипед. Польза от них тоже есть — вы начинаете понимаете все подводные камни, что и зачем нужно. Но писать каждый раз велосипед — не хорошо. А прикатить его из другого проекта нельзя т.к. код продан и является собственностью заказчика.
Конечно ваш код можно оформить в либу, опубликовать ее, в договоре с заказчиком написать, что юзаем либу. Но написать либу на все случаи жизни не так просто, тем более зачем делать еще один велосипед если уже есть, а баги уже найдены и пофикшены. Так в конце концов произошло и со мной.
Я не то что противник рефлекшена, но предпочитаю либы которые генерят код. Его удобно дебажить и всегда можно посмотреть, что там творится.
Ко всему прочему мне нравятся аннотации. Вся моя подборка библиотек практически соответствует этому принципу. Начнем…
Вот эта библиотечка реализует command service.
Я предпочитаю писать статический метод запуска команды и типизировать калбек.
Хотя калбеком может быть любой object я предпочитаю типизировать его. Это поможет компилятору помогать нам.
И оградит вас от соблазна навесить калбеки на паблик методы активити, тем самым нарушив один из столпов ООП — инкапсуляцию :)
По тем самым соображениям и нужен статический метод запуска.
Очень простой инструмент для вызова REST сервисов, как хорошо написанных так и не очень. Код правда не генерит, но очень прост.
Я смотрел в сторону Spring Android, но как-то тяжеловат он.
ну и вот так юзаем
можно подставить свой конвертер, http клиент и кучу всего прочего.
Генерит базу и контент провайдер по аннотациям.
Моя поделка, меня устраивает полностью. Пару статей есть на хабре — тут и тут.
Недавно с пинка evilduck опубликовался в maven central
Смотрел на ORMLite, но мне кажется не подходит оно для андроид. Обычно нам не нужно вытягивать прям все и вся. Обычный sql и вьшки решают почти все.
Очень долго присматривался к этой либе и недавно решился заюзать ее в продакшене — понравилось, несмотря на то, что надо юзать нагенеренные классы.
Мощнейший инструмент, главное не юзать Background ну или включать голову.
Хороший плюс — можно почти безболезненно выпилить.
Смотрел на AQuery и Dagger, но имхо Android Annotations — уже имеет все это.
Единственной минус — иногда тяжело искать ошибку «почему не компилируется?».
О всех возможностях можно прочитать на офф сайте. Единственное, что хочу добавить — всегда пишу статический метод для запуска активити, создания фрагмента. Это изолирует весь код в одном месте и никто в коде не знает юзаем мы нагенеренный класс или оригинальный.
Например фрагмент AlertDialogFragment будет иметь метод
а везде в коде вызов будет типа
и никто не знает о существовании AlertDialogFragment_
Совсем недавно нашел эту замечательную либу. Т.к. мы юзаем лоадеры везде и всюду, обычно результат это Cursor. Всегда можно заюзать CursorAdapter и отобразить то, что надо.
Но вот эта либка предлагает нам юзать список(List), но над курсором, а со списком всеми любимый ArrayAdapter.
Вот такой симбиоз — вы как бы видите List и юзаете ArrayAdapter, но по факту это курсор и курсор адаптер. Настоящая «уличная магия» :)
Ребята не поленились и написали такой себе LazyList с небольшим кешем внутри, все как и положено — внутри LruCache.
Для того что бы получить List вместо Cursor надо написать функцию(transform) конвертации строки курсора в объект и вы получите лоадер который вернет не Cursor, а List
Но как мы знаем иногда надо прочитать курсор в какой-то объект, например надо посчитать что-то, для этого у ребят есть метод wrap
вот такой нехитрый способ.
Это очень тривиальный пример. Возможности гораздо круче.
При этом в метод wrap все еще выполняется в другом потоке. так что вы можете еще сходить в БД за дополнительными данными.
Как я и сказал это иногда надо.
Понятно, что для самого UI используется еще кучка разных либ(в основном компоненты), но это уже зависит от фантазии дизайнера :)
Ну и вот такой кусочек билд скрипта для грейдл, что бы apt завелся(да-да уже есть спец плагин, но его еще не пробовал).
Ах, да — качаем android-db-commons-0.1.6.jar в папку libs
Всем спасибо за внимание
Список вырисовывается такой:
Если заинтересованны прошу под кат.
Начать надо с того,
Естественно, все продиктовано архитектурой приложения. Исходя из моего опыта, почти всегда, надо иметь следующую структуру приложения:
Как видно, приложение делится на 3 слоя — «мордочка», хранилище данных и сервис для асинхронных команд, где почти всегда скрыта логика.
Я не хочу разводить холивар и спорить об архитектуре, я просто описываю то как делаю это я и как делают мои коллеги.
Пару слов почему так
Контент провайдер очень мощная штука с уведомлениями об изменениях данных и это все из коробки. Юзаем в основном как обвертку над базой. Способа как туда правильно впихнуть загрузку данных из инета не нашел.
Сервис асинхронных команд — из названия все ясно, выполняет действия асинхронно(в другом потоке). Практически все действия должны быть асинхронны — запись в базу, походы в инет, да и подсчеты.
Почему «асинхронных команд»? Тут тоже все просто — каждое действие — законченная команда. Которая знает с какими параметрами запуститься, что с ними делать и оповещением о своем завершении.
Вот тут evilduck уже все детально описал.
«Мордочка» — набор активити/фрагментов для отображения данных. Хочу заметить, что вся загрузка данных из хранилища должна быть асинхронная(ни каких походов в базу из UI даже за одним числом). И тут нам на помощь приходит лоадер менеджер — тоже фича из коробки. Стоит смотреть в сторону CursorLoader
Велосипед
Наверное, каждый программист делал свой велосипед. Польза от них тоже есть — вы начинаете понимаете все подводные камни, что и зачем нужно. Но писать каждый раз велосипед — не хорошо. А прикатить его из другого проекта нельзя т.к. код продан и является собственностью заказчика.
Конечно ваш код можно оформить в либу, опубликовать ее, в договоре с заказчиком написать, что юзаем либу. Но написать либу на все случаи жизни не так просто, тем более зачем делать еще один велосипед если уже есть, а баги уже найдены и пофикшены. Так в конце концов произошло и со мной.
Я не то что противник рефлекшена, но предпочитаю либы которые генерят код. Его удобно дебажить и всегда можно посмотреть, что там творится.
Ко всему прочему мне нравятся аннотации. Вся моя подборка библиотек практически соответствует этому принципу. Начнем…
Groundy
Вот эта библиотечка реализует command service.
- Команды можно кенселить
- Любители AsyncTask'ов не заметят перехода
- Есть поддержка калбеков и они очень просты в использовании
Я предпочитаю писать статический метод запуска команды и типизировать калбек.
Хотя калбеком может быть любой object я предпочитаю типизировать его. Это поможет компилятору помогать нам.
И оградит вас от соблазна навесить калбеки на паблик методы активити, тем самым нарушив один из столпов ООП — инкапсуляцию :)
По тем самым соображениям и нужен статический метод запуска.
public class LoginCommand extends GroundyTask{
private static final String ARG_PASSWORD = "arg_password";
private static final String ARG_USER = "arg_username";
@Override
protected TaskResult doInBackground() {
String userName = getStringArg(ARG_USER);
String password = getStringArg(ARG_PASSWORD);
//do something
return succeeded();
}
public static void start(Context context, BaseLoginCommandCallback callback, String login, String password) {
Groundy.create(LoginCommand.class)
.arg(ARG_USER, login)
.arg(ARG_PASSWORD, password)
.callback(callback)
.queueUsing(context);
}
public static abstract class BaseLoginCommandCallback{
@OnSuccess(LoginCommand.class)
public void handleSuccess(){
onLoginSuccess();
}
@OnFailure(LoginCommand.class)
public void handleFailure(){
onLoginError();
}
protected abstract void onLoginSuccess();
protected abstract void onLoginError();
}
}
Retrofit
Очень простой инструмент для вызова REST сервисов, как хорошо написанных так и не очень. Код правда не генерит, но очень прост.
Я смотрел в сторону Spring Android, но как-то тяжеловат он.
public interface ServicesFootballua {
String API_URL = "http://services.football.ua/api";
@GET("/News/GetArchive")
NewsArchive getNewsArchive(@Query("pageId") long pageId,
@Query("count") long count,
@Query("datePublish") String date);
}
ну и вот так юзаем
private static ServicesFootballua API = new RestAdapter.Builder()
.setServer(ServicesFootballua.API_URL)
.build()
.create(ServicesFootballua.class);
...................................
archive = API.getNewsArchive(PAGE_ID, COUNT, dateFormat.format(getTodayTime()));
можно подставить свой конвертер, http клиент и кучу всего прочего.
AnnotatedSQL
Генерит базу и контент провайдер по аннотациям.
Моя поделка, меня устраивает полностью. Пару статей есть на хабре — тут и тут.
Недавно с пинка evilduck опубликовался в maven central
Смотрел на ORMLite, но мне кажется не подходит оно для андроид. Обычно нам не нужно вытягивать прям все и вся. Обычный sql и вьшки решают почти все.
Android Annotations
Очень долго присматривался к этой либе и недавно решился заюзать ее в продакшене — понравилось, несмотря на то, что надо юзать нагенеренные классы.
Мощнейший инструмент, главное не юзать Background ну или включать голову.
Хороший плюс — можно почти безболезненно выпилить.
Смотрел на AQuery и Dagger, но имхо Android Annotations — уже имеет все это.
Единственной минус — иногда тяжело искать ошибку «почему не компилируется?».
О всех возможностях можно прочитать на офф сайте. Единственное, что хочу добавить — всегда пишу статический метод для запуска активити, создания фрагмента. Это изолирует весь код в одном месте и никто в коде не знает юзаем мы нагенеренный класс или оригинальный.
Например фрагмент AlertDialogFragment будет иметь метод
public static void show(FragmentActivity activity,
DialogType type, int titleId, String msg, int positiveTitleId,
OnDialogClickListener positiveListener) {
DialogUtil.show(activity, DIALOG_NAME,
AlertDialogFragment_.builder()
.titleId(titleId)
.errorMsg(msg)
.positiveButtonTitleId(positiveTitleId)
.dialogType(type).build()
).setOnClickListener(positiveListener);
}
а везде в коде вызов будет типа
AlertDialogFragment.show(BaseActivity.this,
DialogType.CONFIRM,
R.string.some_title,
getString(R.string.some_message),
R.string.btn_edit,
new OnDialogClickListener() {...............}
и никто не знает о существовании AlertDialogFragment_
Android db-commons
Совсем недавно нашел эту замечательную либу. Т.к. мы юзаем лоадеры везде и всюду, обычно результат это Cursor. Всегда можно заюзать CursorAdapter и отобразить то, что надо.
Но вот эта либка предлагает нам юзать список(List), но над курсором, а со списком всеми любимый ArrayAdapter.
Вот такой симбиоз — вы как бы видите List и юзаете ArrayAdapter, но по факту это курсор и курсор адаптер. Настоящая «уличная магия» :)
Ребята не поленились и написали такой себе LazyList с небольшим кешем внутри, все как и положено — внутри LruCache.
Для того что бы получить List вместо Cursor надо написать функцию(transform) конвертации строки курсора в объект и вы получите лоадер который вернет не Cursor, а List
return CursorLoaderBuilder.forUri(URI_ITEMS)
.projection(ItemConverter.PROJECTION)
.where(ItemTable.ACTIVE_STATUS + " = ?", 1)
.where(ItemTable.DESCRIPTION + " like ?", "%" + searchText + "%")
.transform(new ItemConverter()).build(getActivity());
Но как мы знаем иногда надо прочитать курсор в какой-то объект, например надо посчитать что-то, для этого у ребят есть метод wrap
public Loader<Integer> onCreateLoader(int i, Bundle bundle) {
return CursorLoaderBuilder
.forUri(ITEMS_URI)
.projection("count(" + ItemTable.GUID + ")")
.where(ItemTable.ACTIVE_STATUS + " = ?", 1)
.where(ItemTable.STOCK_TRACKING + " = ?", 1)
.where(ItemTable.TMP_AVAILABLE_QTY + " <= " + ItemTable.RECOMMENDED_QTY)
.wrap(new Function<Cursor, Integer>() {
@Override
public Integer apply(Cursor c) {
if (c.moveToFirst()) {
return c.getInt(0);
}
return 0;
}
}).build(DashboardActivity.this);
}
вот такой нехитрый способ.
Это очень тривиальный пример. Возможности гораздо круче.
При этом в метод wrap все еще выполняется в другом потоке. так что вы можете еще сходить в БД за дополнительными данными.
Как я и сказал это иногда надо.
Окончание
Понятно, что для самого UI используется еще кучка разных либ(в основном компоненты), но это уже зависит от фантазии дизайнера :)
Ну и вот такой кусочек билд скрипта для грейдл, что бы apt завелся(да-да уже есть спец плагин, но его еще не пробовал).
Ах, да — качаем android-db-commons-0.1.6.jar в папку libs
ext.androidAnnotationsVersion = '2.7.1';
configurations {
apt
}
dependencies {
compile fileTree(dir: 'libs', include: '*.jar')
compile 'com.google.guava:guava:13.0.1'
compile 'com.telly:groundy:1.3'
apt 'com.telly:groundy-compiler:1.3'
apt "com.googlecode.androidannotations:androidannotations:${androidAnnotationsVersion}"
compile "com.googlecode.androidannotations:androidannotations-api:${androidAnnotationsVersion}"
compile 'com.github.hamsterksu:android-annotatedsql-api:1.7.8'
apt 'com.github.hamsterksu:android-annotatedsql-processor:1.7.8'
}
android.applicationVariants.all { variant ->
aptOutput = file("${project.buildDir}/source/apt_generated/${variant.dirName}")
variant.javaCompile.doFirst {
aptOutput.mkdirs()
variant.javaCompile.options.compilerArgs += [
'-processorpath', configurations.apt.getAsPath(),
'-processor', 'com.annotatedsql.processor.provider.ProviderProcessor,com.annotatedsql.processor.sql.SQLProcessor,com.googlecode.androidannotations.AndroidAnnotationProcessor,com.telly.groundy.GroundyCodeGen',
'-AandroidManifestFile=' + variant.processResources.manifestFile,
'-s', aptOutput
]
}
}
Всем спасибо за внимание