Кавказская кухня: проблемы и решения

    Привет всем хабровчанам! Рад сообщить вам, что компания, в которой я работаю, выпустила приложение «Кавказская кухня» под Android. Теперь любимые блюда будут всегда у вас под рукой. Я занимался почти всем процессом разработки приложения и хотел бы поделится деталями с вами. Вначале хочется рассказать о самом приложении, а во второй части статьи о проблемах возникших при разработке и возможных вариантах решения. В итоге, те кому интересно приложение могут прочесть первую часть, а те кому интересен процесс разработки вторую. Поехали!

    Обзор приложения

    Скриншоты и само приложение можно посмотреть как обычно в маркете — market.android.com/details?id=net.octobersoft.android.caucasiancuisine

    С помощью приложения вы сможете быстро найти свой любимый рецепт и способ приготовления(в базе чуть больше 200 рецептов). Кроме того, с помощью помощника вы можете быстро найти рецепты по имеющимся у вас продуктам, с подсказкой сколько ингредиентов содержатся в найденном рецепте. С помощью корзины вы сможете сохранять нужные вам продукты, либо добавлять свои, после чего начиная приготовления какого-либо рецепта поставить таймер и засучив рукава начать готовить. Добавить ингредиенты в корзину можно при просмотре рецепта(в первой вкладке после выбора категории рецепта и понравившегося рецепта), а новые добавленные в корзину продукты будут отображаться в помощнике со всеми остальными. И конечно, все понравившиеся рецепты можно держать в избранном, для быстрого доступа к ним. При выборе всех рецептов появляется поисковый бар, который поможет вам быстро найти нужный рецепт. Такое вот удобное приложение, которое поможет вам приготовить любимое блюдо.

    Добавлю, что скоро будет обновлена версия приложения, в которой появится возможность поделиться мыслями об использовании приложения со своими друзьями через фейсбук, твиттер или вконтакте и пару других плюшек.
    Если вы заметите какие-нибудь баги, просьба написать об этом в комменты или почту.

    Разработка. Возникшие трудности и возможные решения

    Табы и несколько окон
    Как вы могли заметить, навигация по приложению осуществляется с помощью табов. Но во многих вкладках есть несколько окон. Сложность заключается в том, что если не чистить запущенные окна при переходах, то иерархия вьюшек привысит допустимое значение и приложени упадет. Для этого можно создать свой класс наследуемый от TabActivity(который наследует главное окно приложения, где все вкладки), который переопределит onBackPressed() нужным образом, либо вызывать самостоятельно finish() при переходах.
    Также, была проблема с кастомизацией внешнего вида для одной вкладки. На самом деле сложного в этом нет ничего, вы просто пишите свой лэйаут с вьюшками и стилизуете его как угодно передавая в таб.

    Состояния активити

    Как утверждают некоторые авторы, чаще всего(и все что вам в основном необходимо) используются состояния onCreate(само собою), onStart() и onPause(). Да, действительно это так, но в нашем приложении таймер работает еще и в фоновом режиме и при разрушении окна(когда вызывается onDestroy(), он сбрасывает состояние в базу, чтобы потом возобновиться при запуске приложения). Авторы Pro Android 2 утверждают что данный метод, может и не вызываться во все, как и onStop(), так что будьте внимательны и изучите жизненный цикл окна досконально. Можно было написать для таймера службу, как вариант решения проблем, но тогда могли бы возникнуть и другие.

    Градиентные изображения

    Некоторые изображения в нашем приложении содержат градиенты и по-умолчанию(по крайней мере у меня в эмуляторе и на реальных устройствах) градиенты расплываются в полосы, что выглядит жутковато. Решение проблемы как выяснилось простое: задать палитру окна. А сделать это можно переопределив метод активити onAttachedToWindow() добавив следующие строки в метод:

    super.onAttachedToWindow(); //не забывайте всегда вызывать версии методов для суперкласса!
    Window currWind = getWindow();
    currWind.setFormat(PixelFormat.RGBA_8888);


    Не забывайте про Handler

    Если потребуется отображать что-либо в потоке UI, а вы находитесь не в контектсе активити(это конечно же не Android-way), вы не сможете обойтись беэ этого класса. Другой вариант — воспользоваться AsyncTask. Хотя вариантов использования данного класса гораздо больше(к примеру очередь потоков, обновляющих элементы UI).

    Вызов событий клавиатуры программно

    Иногда такое может потребоваться и решается например так:
    new BaseInputConnection(txtView,false).sendKeyEvemt(backPressedKeyEvent);

    Уверен есть и другие способы, но вернуться на предыдущее окно после неудачного поиска мне помог код выше.

    Кэш SQLite

    SQLite кэширует данные. Просто необходимо об этом помнить во время разработки приложения и ставить галочку wipe user data(если хотите обновить данные). В свое время я потратил несколько часов на понимание почему так, не зная этой особенности.

    Списки

    Наследовать от ListActivity или нет? На самом деле, разница только в том нужны ли в дизайне окна другие элементы и собственно сам лэйаут. Если да, то наследование вам не к чему, если нет, то наследуйте класс и setContentView вызывать ненужно.
    Также следует помнить одну важную вещь про списки. Список использует повторно элементы вью, поэтому работать с ним нужно внимательно. Можно например воспользоваться методами вью: setTag(), getTag() для того чтобы привязать произвольные идентификаторы. Также следует внимательно отнестись к написанию пользовательского адаптера и использования в методе getView() параметра convertView, собственно про это и я писал выше!

    Другой проблемой при работе со списками было некорректное отображение элементов списка в ScrollView.
    Макет включает такой кусок:
    ...
    <ScrollView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content">
    <LinearLayout
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <ImageView
    android:src="@drawable/icon"
    android:id="@+id/recipe_img"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:scaleType="fitXY"
    android:adjustViewBounds="true"
    android:layout_weight="0"/>

    <ListView
    android:id="@android:id/list"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:scrollbars="none"
    android:divider="#00000000"
    android:cacheColorHint="#00000000" />

    <ImageView
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:layout_weight="2"
    android:gravity="center"
    android:scaleType="fitXY"
    android:src="@drawable/cook"/>
    ...


    В таком варианте дизайна элементы списка усекаются и видны не все(по-моему есть такой issues на гуглкоде)
    Решение проблемы было задать размер списка по кол-ву элементов в нем, код ниже:
    //set ListView size by items count
    public static void setListViewHeightBasedOnChildren(ListView listView) {
    ListAdapter listAdapter = listView.getAdapter();
    if (listAdapter == null) {
    // pre-condition
    return;
    }

    int totalHeight = 0;
    for (int i = 0; i < listAdapter.getCount(); i++) {
    View listItem = listAdapter.getView(i, null, listView);
    listItem.measure(0, 0);
    totalHeight += listItem.getMeasuredHeight();
    }

    ViewGroup.LayoutParams params = listView.getLayoutParams();
    params.height = totalHeight + (listView.getDividerHeight() * (listAdapter.getCount() - 1));
    listView.setLayoutParams(params);
    }


    Диалоги

    При написании кастомного диалога(наследовав класс Dialog) возникла трудность: кнопки легко нашлись по findViewById() и корректно работали, но виджет EditText нет(метод возвращал null). Для поиска корневого элемента, как обычно, использовался LayoutInflater. Выход был прост — создать класс Dialog, передать нужный лэйаут и потом уже вызывать от диалога findViewById(). И кстати, контекст приложения передать так и не удалось как я не пытался, потому что диалоги привязаны к текущему активити. Но подозреваю все же, что можно решить и эту проблему!

    Не изобретайте велосипед

    Много готовых классов и приложений уже есть в Android и потому лучше сперва посмотреть внимательно доку или погуглить.

    Используйте LogCat

    Логгирование помагает быстро найти ошибку при разработке(особенно используя свои фильтры в Eclipse) и просмотреть необходимый вывод активити, служб и т.д., потому всегда используйте его. Но из релиз-версии логгирование нужно убрать(как говорит гугл), хотя куча стандартных сервисов и приложений на устройстве все равно пишут в лог!

    SQLite Manager

    Я использовал данный плагин к Firefox 3.х для работы с базой sqlite. Он бесплатен, удобен, функционален(можно писать процедуры, смотреть структуры таблиц, данные, выполнять запросы и т.д.). Так что всем кто еще не знает или хочет выбрать подобный инструмент рекомендую, для разработки самое то. Хотя и альтернатив куча!

    Ограничение на размер приложения

    Это самый забавный пункт. В день или точнее час релиза в маркет мы узнаем, что приложение не может быть больше 50Мб, а наше весит 79! Спасибо тебе Гугл за такую фишку… из-за этого пришлось сделать не самое приятное решение…

    Скриншоты

    Так выглядит окно с рецептами в выбранной категории:
    image

    Экран со всеми категориями рецептов:
    image

    Экран выбранного рецепта:


    А так выглядит окно с таймерами:


    Заключение

    Подчеркну, что мои рекомендации могут быть оспорены, так как я не являюсь гуру в Android(это мое первое приложение и по совместительству первый пост на Хабре) и в платформе часто встречается несколько путей решения одной проблемы, но рассмотренные советы работают и это радует! Поддерживаю призыв к обмену опытом, который уже был на Хабре.

    В остальном, процеcс разработки был примерно как в веб-приложениях: пишется модель данных, CRUD-операции для этой модели, интерфейс и необходимый функционал. Трудности были по незнанию платформы Android и отсутствия опыта в создании приложений под мобильные платформы.

    Замечания и пожелания по топику в личные сообщения.
    Спасибо за внимание!

    UPD Теперь приложение доступно и в бесплатной версии:
    market.android.com/details?id=net.octobersoft.android.caucasiancuisinefree
    Поделиться публикацией
    Ой, у вас баннер убежал!

    Ну. И что?
    Реклама
    Комментарии 63
    • 0
      Приложение хорошее (не тестил но пригодилось бы) но жаль что платное :)
      • +4
        Ну довольно скромная стоимость за довольно непростой временами труд, согласитесь:)
        • 0
          Нет с этим я даже не спорю :) Но думаю если к примеру сделать 2 версии то уже было бы по лучше к примеру пусть в бесплатной версии будет 100 рецептов а в платной 200. Ну и к примеру убрать таймер это уже даст Вашему приложению не плохой старт
          • 0
            Круто было бы отобрать самые классные рецепты и заюзать in-app billing :-)
            • 0
              Рендом ))) и по закону подлости нужного рецепта может и не оказаться ))))
            • 0
              Да, идея хорошая, согласен. Но я разработчик, потому подобными вопросами не занимаюсь:)
        • 0
          размер приложения большеват даже очень. возможно было бы лучше держать рецепты, картинки и другую инфу на серверной стороне так и обновлять базу было бы проще(без обновления приложения). И сделать функцию чтобы можно было выбрать рецепт и сделать его доступным в оффлайне. Не куплю и не скачаю только из-за размера а то даже игры и то меньше или кэш потом качают.
          • +2
            К примеру, AngryBirds весит 19 метров, а хранить картинки на сервере и подгружать постоянно не лучшее решение чем раз скачать и пользоваться! Вообще, дело ваше, всем угодить не получится. А обновлять приложения и так не сложно.
            • –1
              По поводу размера это да. Слишком оно большое. Лучше сделать клиент серверное приложение. как предложил irafa
              • +1
                А трафик у юзера хавать постоянно лучше по вашему? Или я не понимаю чего-то? В чем разница скачать раз или подгрузить постепенно?
                • –1
                  Да бросьте. Сейчас куда не ткнись везде в домах стоят wifi(у меня из всех знакомых только у 4 не стоит) хотя по кол-во кого припомню около 30 человек. продолжу по поводу трафика. Вы посмотрите сколько люди трафика убивают на соц сети и не жалуются а на 1 рецепт пусть на 2 или даже на 3 потратят и не обеднеют. Единственное но. есть планшеты без 3G, вот для них да проблема. Но хотя по статистике 6-7 из 10 пользуются вай фаем дома. так что это не самая большая проблема на сегодняшний день
                  • +1
                    К вашему сведению у меня нет вай-фая и нормально живу:) Так что снова обратная сторона и не довод в пользу размера.
                    Я не спорю, что оно весит много… и мне это тоже не по душе, но везде есть свои плюсы и минусы: как в нашем варианте, так и вашем варианте решения проблемы.
                    • 0
                      Да и хочу заметить! У моих 2 сестер. Обоих htc не важно какие модели. Когда они ко мне приезжают я обоим им апдейтю софт и прошивку т.к. им вообще похрену что это и зачем это им нужно. Вот Вам пример из жизни что при пополнении базы и обновлении приложения в маркете не все могут получить их. Да и кстати еще одно но, при том как сложилась ситуация с маркетом в 50 метров. Как Вы собираетесь избавляться от будущей проблемы.
                      • 0
                        Какой именно? Ограничение в 50 метров?

                        Будем проект вести иначе с самого начала, учитывая эту особенность.
                        А когда узнаешь в час релиза, то уже не до этого:)
                        • 0
                          Коль такая проблема появилась советую Вам собрать инфу по девайсам с андрюхой, версий прошивок и внутренней памяти, и подведете не большие итоге по поводу клиент-серверного приложения основываясь на этом я думаю Вы уже разберетесь как дальше вести разработку
                    • +2
                      А при чем тут wi-fi? Это же фактически книга рецептов, которую может понадобиться открыть, например, в каком-нибудь супермаркете, где не то что унылый 3Г, а вообще сотовая сеть ловится с трудом. Если бы не технические ограничения, вряд ли бы кто-то пожелал серверное приложение вместо полной базы на клиенте.
                    • +2
                      Проблема большого приложения в том что оно жрёт внутреннюю память телефона, а производители телефонов что-то жадны на её размеры. Поставишь пяток таких приложений — и всё. А установка приложений на SD-карту поддерживается только с 2.3 (это если без шаманств с рутом и app2sd). А апгрейд ОС до 2.3 далекооо не у всех устройств возможен. Поэтому единственное правильное и удобное для пользователя решение — делать маленькое приложение, а большую базу выносить на SD-карту.
                      • 0
                        Поправлю: с версии 2.2, а их процент уже более 60%. Причём перенос должен быть разрешён разработчиком (хотя при наличии рута это не важно).
                        Однако, верно: например, одно-два больших приложений, которые только распаковывают себя на флешку, после чего удаляются. А основное — с минимальным набором данных, максимально лёгкое. Некоторые игры так работают (доп.уровни) или, например, словари (доп.базы).
                        • 0
                          360 Кб пямяти приложение жрет сейчас(так отображается в диспетчере задач), и ставится на карточку. А сейчас новые девайсы почти все идут с картой SD. Да, со старыми может быть проблема при малой памяти.
                    • 0
                      Изображения в png? Если да, то имеет смысл обработать их оптимизатором вроде pngout

                      Не всегда получается с его помощью уменьшить размер файла, но в большинстве случаев это удаётся сделать. И без потери качества.

                      Очень большого выигрыша в размере не будет, но на процентов двадцать можно будет сделать размер приложения меньше.
                  • +2
                    29 mb?! Вы туда картинки ко всем рецептам запихнули?

                    Не очень понятно что Вы подразумеваете под «окном»? Активити?
                    Не понял зачем наследоваться от TabActivity, только для того, чтобы переопределить onBackPressed?
                    • 0
                      Да, все картинки к рецептам! Что вам не нравится?
                      Да, подразумевается активити.
                      Не совсем. Наследуем и переопределяем onBackPressed(), пишем свой метод startChildActivity(), а в них работаем со списком идентификаторов окон и чистим в нужном состоянии.
                      • +2
                        Как то Вы агрессивно настроены на критику.
                        Могу Вас сказать одно сейчас очень много моделей с андрюхой у которых к примеру 2.1 без возможности переноса на флешку приложений это первое. Да Да Да, я знаю что прошивок достаточно но поверьте, каждый день я вижу планшеты которые мне приносят домохозяйки и которым по одному место на их модификацию. В третьих на многих моделей память внутренняя не очень большая(512) и не каждый захочет тратить 30 метров для этого.
                        • 0
                          Устал потому что, с каждым бывает, сами знаете.

                          Скажу что это приложение под айфон(а я его переносил именно с айфона) весит 120 метров. Видимо домохозяйки совсем в шоке?!)

                          Весь этот спор обратная сторона: либо место, либо трафик.

                          Все пожелания и критика учитывается, спасибо!
                          • 0
                            Ну по поводу размера на iphone/ipad меня совсем не удивляют. Но для «Андрея» это нужно учитывать :)
                            • 0
                              А меня вот удивило. Уже 120 метров для меня точно перебор:)
                              • 0
                                Там, кажется, чуть ли не полу-гиговые аппы есть (помнится, то был словарь какой-то огромный), но там-то нет возможности переноса на флешку (и её нет), да и памяти обычно много (а не с сотню метров, как на некоторых андроидных девайсах).
                        • +2
                          Основная неприятность большого размера, как правильно уже заметили комментами выше, именно в сильно ограниченном размере внутренней памяти, у большинства моих знакомых с ондройдами его почти что уже нет, да и на моём нексусе постоянно уже приходится скурпулёзно проги переносить на sd карту, чтобы место освободить. А у кого и такой возможности нет? Т.е. вы отсекаете своим решением большое количество пользователей. ИМХО, всё же правильней динамически подгружать, сколько посмотрел — столько весит.
                          На игры в данном случае не корректно кивать, т.к. там всё графика, скорость и тп, да и не ожидают люди от игры, что она начнёт посреди игры что то в инете подгружать. А в таких прогах это уже нормальным считается. И кстати при подгрузке фоток их можно уже сразу класть на sd карту.

                          Пожелание такое, всё же пользоваться устоявшейся терминологией(активити, активность), встретив слово окно, долго не мог вкурить, о чем речь))
                          • 0
                            Как вариант — закачивание рецептов пачками/категориями и по одному. Во-первых, так можно будет не обновлять приложение при добавлении новых рецептов, а, во-вторых, уменьшится вес программы.
                        • 0
                          Не думали стилизовать всякие EditText'ы под общий концепт приложения?
                          • 0
                            Нет, но хорошая идея, хотя наш дизайнер явно не обрадуется:)
                            • 0
                              Это скорее работа программиста, не :-)
                              State-list'ы + Shape Drawable в xml в качестве фона?
                              • 0
                                Я думал речь о картинках фоновых для виджетов:)
                              • +1
                                Вашему дизайнеру явно нужно почитать гайдлайны на
                                Видно что приложение в стиле iOS, не андройд совсем, табы снизу, не очень правильно используется пространство где тайтлы и т.д. и т.п.
                                • –3
                                  Вы правы. Пожелание было: максимум похоже под айфон-версию!
                                  • 0
                                    Табы снизу это практично, я не понимаю, нафига через весь экран надо тянуться к ним наверх. Просто стоит привести стиль к более андроидному…
                                    • 0
                                      Что вы под более андроидными понимаете?
                                      • 0
                                        Ну, в данный момент цветовая гамма и форма контролов кричит о том, что это было притянуто за уши с iOS. Общий стиль табов не тот, хотя именно расположение снизу удобнее, факт.
                                        • –1
                                          Как уже писал: требование такое — максимально похожий перенос готового приложения айфонового под дроид.

                                          Непонятно только почему вам не нравится стилизация табов, ибо на StackOverflow не раз люди искали ответы на то как сделать примерно похожее. А кастомизировать их можно как угодно. Ну поменяется общий фон, картинки и вы тоже скажите, что они похожи на айфоновые?
                                          • 0
                                            нет смысла писать IPhone like табы. Пользователь должен видеть табы, как в большинстве Android приложений, к которым он привык.
                                            Насчет положения табов здесь есть дискуссия на эту тему. Из минусов — табы внизу могут быть перекрыты меню, всплывающей клавиатурой, пользователь может случайно наживать кнопки телефона (back, home) при нажатии таба внизу.
                            • 0
                              > приложение не может быть больше 50Мб, а наше весит 79! Спасибо тебе Гугл за
                              > такую фишку… из-за этого пришлось сделать не самое приятное решение…
                              В статье не увидел — как решили-то проблему?
                              Первое что приходит мне на ум — перенести базу (или её часть) на SD-карту и подгружать с сервера один раз при первом запуске приложения.
                              • 0
                                Выбросили картинки под айфон, которые были с префиксом 2x и вставили меньшие:)

                                • 0
                                  Т.е. там *каждая* фотография ещё и дублировалась? Эпично… Думаю, процессор бы не перегрелся от ресайза картинок при открытии рецепта ))
                                  • +1
                                    Нет, там были только большие картинки и выглядели везде(на всех размерах экрана) очень приятно.
                              • НЛО прилетело и опубликовало эту надпись здесь
                                • –2
                                  Нативные табы так спрятаны, что далеко не каждый их найдет.
                                  И вообще с ними бардак, на одном девайсе они показываются так, на другом — эдак (с подписями и без, разного размера и т.п.).
                                  Нам приходили комментарии от людей, которые думали, что приложение ограничивается только тем, что видно на стартовом экране и не знали, что есть еще кнопки меню с выбором вкладок.
                                  Решили перейти на собственный интерфейс с табами.
                                  • НЛО прилетело и опубликовало эту надпись здесь
                                    • +1
                                      Вы не путаете меня с автором топика? Так как вы вряд ли видели наше приложение, оно только в Японии доступно.
                                      • НЛО прилетело и опубликовало эту надпись здесь
                                • +1
                                  Mmmm… Yummy!
                                  • 0
                                    И спасибо, что гадите в карму:) Вот так стараться — писать посты!
                                    • +1
                                      что-то я вашего извращения с листвью не понял — у вас лист на длиной всего кол-ва элементов и скролите вы скроллвью? полный трындец. основная идея статьи — с андроидом я не разобрался — но как то сделал.
                                    • +1
                                      Во-первых то только часть макета(разметки), во-вторых не знаю как обстоит дело сейчас, но вот такая вот штука проявилась у нас:
                                      code.google.com/p/android/issues/detail?id=6552
                                      • 0
                                        Везде пишут (и сами разработчики на Google IO рекомендовали), что не нужно запихивать ListView в ScrollView. Даже в баге по ссылке это написано первым же комментом. И посмотрите, пожалуйста, на статус ошибки — это вам о чём-нибудь говорит?
                                        Объясните, где вам могло понадобиться вставлять ListView в ScrollView? Может быть, это можно было сделать как-то иначе?
                                        • +1
                                          При просмотре рецепта есть список ингредиентов выше которого картинка рецепта, а ниже способ приготовления, потому все это прокручивается когда описание и список большие.
                                          Да, вполне можно было обойтись и без списка, используя например TextView или другую вьюшку, а статус ошибки говорит «отклоненная», тем не менее она есть и подобном поведении знать нужно.
                                      • 0
                                        Сервис для таймера это:
                                        — лишние строчки кода, написание AIDL интерфейса;
                                        — лишняя память, которая выделяется под сервис;
                                        — не всегда работает, т.к. ОС может убить даже сервис.

                                        Из личного мерзкого опыта :)
                                        • 0
                                          ;) При отключении дисплея девайса, например?
                                          • 0
                                            Нет, отключение дисплея никак не влияет на сервис, при чем тут это?

                                            Я до конца не понял свою проблему. Если приложение долго не вызывалось, а вместо него работали другие, то сервис перезагружается. Если принудительно закрыть Activity, работающее с сервисом, и перезапустить приложение, то все ОК.
                                            • 0
                                              Скорее всего системе нужны были ресурсы и она завершила менее приоритетный сервис, активити. Другого объяснения не знаю:)
                                              • 0
                                                Хех, сервис не завершается (время старта показывается именно то, когда впервые запускается приложение). Тут дело скорее всего в том, что я в приложении неправильно разруливал жизненные цикли из-за табов.
                                                • 0
                                                  Ну это я про активити говорил. Про приоритеты.
                                                  Да, с табами один сплошной гемморой, особенно если внутри одного таба еще и ActivityGroup.
                                        • 0
                                          Коллеги, а скажите чем вы рисуете интерфейсы? Тут я вижу простой ListView, вроде понятно. Мне же нужно делать сложную разметку, использую RelativeLayout, но объекты сильно лезут в стороны… пробовал droiddraw но то что получается, я могу и сам в ADT сделать… посоветуйте плз что нибудь…

                                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                          Самое читаемое