1. Устанавливаем необходимые пакеты
npm i --save-dev redux-devtools redux-devtools-log-monitor redux-devtools-dock-monitor
import React from 'react';
import { createDevTools } from 'redux-devtools';
import LogMonitor from 'redux-devtools-log-monitor';
import DockMonitor from 'redux-devtools-dock-monitor';
export default createDevTools(
<DockMonitor toggleVisibilityKey='ctrl-h' changePositionKey='ctrl-q'>
<LogMonitor />
</DockMonitor>
);
import DevTools from './DevTools';
export default DevTools;
3. “Причешем” rootReducer
Во второй части мы поместили создание корневого редьюсера в configureStore, что не совсем правильно, так как это не его зона ответственности. Сделаем небольшой рефакторинг и перенесем его в redux/reducers/index.js.
Добавляем новую функциональность
Реализуем следующий сценарий
- Пользователь нажимает на кнопку “Запросить время”.
- Показываем индикатор загрузки, кнопка становится неактивной, чтобы избежать нежелательных повторных запросов.
- Приложение делает запрос к API.
- Приложение получает ответ от API и сохраняет полученные данные в глобальное состояние.
- Индикатор загрузки исчезает, кнопка снова становится активной; отображаем пользователю полученные данные.
Это достаточно объемная задача. Чтобы сфокусироваться на отдельных ее частях, сначала реализуем пункты 1,2 и 5, а для 3 и 4 сделаем заглушку.
1. Добавляем действия
После клика по кнопке “Запросить время” мы должны последовательно:
- изменить значение loading с false на true;
- сделать запрос;
- получив ответ, вернуть значение loading с true на false обратно и сохранить либо полученные данные, либо информацию об ошибках.
export const TIME_REQUEST_STARTED = 'TIME_REQUEST_STARTED';
export const TIME_REQUEST_FINISHED = 'TIME_REQUEST_FINISHED';
export const TIME_REQUEST_ERROR = 'TIME_REQUEST_ERROR';
function timeRequestStarted() {
return { type: TIME_REQUEST_STARTED };
}
function timeRequestFinished(time) {
return { type: TIME_REQUEST_FINISHED, time };
}
function timeRequestError(errors) {
return { type: TIME_REQUEST_ERROR, errors };
}
export function timeRequest() {
return (dispatch) => {
dispatch(timeRequestStarted());
return setTimeout(() => dispatch(timeRequestFinished(Date.now())), 1000); // Изображаем network latency :)
};
}
Здесь каждое действие мы оформляем в виде небольшой функции, которая будет изменять глобальное состояние, а timeRequest — комбинация этих функций, которая целиком описывает наш сценарий. Именно ее мы и будем вызывать из нашего компонента.
2. Обновляем код страницы со временем
Добавим кнопку react-bootstrap-button-loader с поддержкой индикатора загрузки на страницу TimePage и научим ее вызывать функцию timeRequest по клику.
1. Передаем глобальное состояние
Механизм очень простой:
- После того, как сервер выполнил всю работу и сформировал контент для клиента, мы вызываем функцию getState, которая возвращает актуальное глобальное состояние. Далее мы передаем контент и глобальное состояние в наш HTML-шаблон и отдаем полученную страницу клиенту.
- Клиентский JavaScript считывает глобальное состояние прямо из глобального объекта window и передает его в configureStore в качестве initialState.
2. Добавляем авторизацию
Устанавливаем redux-oauth
Примечание: мы используем redux-oauth для изоморфного сценария, но она также поддерживает и client-side only. Примеры конфигурации для различных случаев и демо можно найти на сайте библиотеки.
Примечание 2:redux-oauth использует cookie для авторизации, так как механизм local storage не подходит для изоморфного сценария.
npm i --save redux-oauth cookie-parser
Активируем плагин cookieParser для express
4. Добавим кнопки “Войти” и “Выйти”
Как правило, авторизованный пользователь имеет больше возможностей по работе с системой, чем гость, иначе какой же смысл в авторизации?
С технической точки зрения это означает, что многие компоненты могут выглядеть и вести себя по-разному в зависимости от того, зашел пользователь в систему или нет.
Я являюсь ярым сторонником принципа DRY (don’t repeat yourself), поэтому напишем небольшой хелпер.
“Шлифуем” приложение
Итак, что можно улучшить:
- Ссылки на страницу “Время” должны отображаться только для авторизованных пользователей.
- Если пользователь ввел адрес защищенной страницы в браузере, мы перенаправим его на страницу с авторизацией (в нашем случае — HelloWorldPage).
- Если пользователь вышел из системы, мы должны удалить из глобального состояния его данные.
2. Ограничиваем доступ к защищенным страницам
Как мы помним, за соответствие между URL и страницей, которую нужно отрендерить отвечает библиотека react-router, а конфигурация путей находится в файле routes.jsx. Нам нужно добавить следующую логику: если пользователь неавторизован и запросил защищенную страницу, то перенаправим его на HelloWorldPage.
Для получения информации о пользователе нам необходимо передать в routes.jsx ссылку на хранилище глобального состояния.
Auth-сервис
Перейдем от теории к практике и напишем простой сервис, который будет аутентифицировать пользователя и выдавать ему токен доступа. Шаги по созданию проекта ничем не отличаются от предыдущей части, поэтому я их опущу.
Пользователи будут храниться в БД. В идеале это должна быть отдельная БД, но в обучающих целях будем использовать ту же, что использовали в первой части. Опишем DAO пользователя:
@Data
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "clients")
public class ClientEntity {
@Id
@Column(name = "client_id")
private String clientId;
private String hash;
}
Репозиторий будет выглядеть совсем просто:
public interface ClientRepository
extends CrudRepository<ClientEntity, String> {
}
Т.к. мы будем реализовывать Client Credentials Flow в терминах протокола OAuth, то здесь client — это и есть пользователь. Соответственно clientId, clientSecret — аутентификационные данные пользователя. В открытом виде пароль пользователя хранить нельзя, поэтому будем хранить некий хеш, о котором будет написано ниже.
Опишем сервис, который будет регистрировать нового клиента и проверять его аутентификационные данные:
public interface ClientService {
void register(String clientId, String clientSecret);
void checkCredentials(String clientId, String clientSecret);
}
Для правильного хранения паролей в БД будем использовать Bcrypt. Это криптографическая хеш-функция, основанная на шифре Blowfish. Воспользуемся реализацией в библиотеке jBCrypt, добавим зависимость в проект:
dependencies {
...
implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4'
}
Реализуем интерфейс ClientService:
Jwt (json web tokens)
JWT — это стандарт, основанный на JSON, для создания токенов доступа. Он состоит из трех частей: заголовка, тела и подписи:
В заголовке указывается тип токена и алгоритм подписи:
{
"typ": "JWT",
"alg": "HS256"
}
В тело записывается необходимая пользовательская информация (payload). Для аутентификации и авторизации это может быть id пользователя, его роль, время действия токена:
Kangoo club kaluga. приложение для фитнес-клуба с расписанием, записью и бронированием униформы
- Платформа: Adalo
- Время на разработку: 2 недели
- Затраты: 12$ по базовому тарифу
Сергей — владелец веб-студии и ни разу не писал код. Но в конце прошлого года с помощью Adalo за две недели назерокодил полноценное мобильное приложение для одного необычного фитнес-клуба и загрузил их в сторы.
Особенность клуба в том, что для тренировки нужно арендовать специальные ботинки Kangoo: их ограниченное количество, и может не достаться нужный размер. Поэтому нужно было создавать своё приложение.
Внутри приложения есть онбординг новых пользователей, личный кабинет для оплаты кампаний на самых популярных площадках с бонусами, календарь тренировок и бронирование обуви, запись и отмена записи на занятия, социальные механики, push-уведомления, админка для сотрудников клуба, профиль клиента с информацией об абонементе.
Благодаря приложению и сообществу ученики сильнее погружаются в обучение, а преподавателям проще размещать материалы и общаться с учениками. Каталог учеников и чаты помогают фотографам нарабатывать профессиональные связи.
Make. мобильная методичка по макияжу по подписке на glide
- Платформа: Glide
- Время на разработку: 2 недели (большая часть — наполнение базы данных)
- Затраты: 12$ (базовый тариф в Glide)
Игорь — профессиональный программист. Как-то раз ему понадобилось выполнить техническую задачу за пару дней — так он вошел в зерокодинг. Сначала автоматизировал на Integromat, потом перешел на Glide. А в пандемию он назерокодил приложение для обучения макияжу MAKE — помогал жене перевести бизнес в онлайн.
Игорь освоил Glide за три дня, еще 4 дня делал структуру приложения. Дольше всего вносил список из 400 продуктов — это заняло 2 недели😂 Приложение интегрировано с ЮKassой, Integromat и GetCourse, можно выбрать свой цветотип, форму лица и глаз, найти инструменты и средства для макияжа, а также получить советы — где их лучше купить, чтобы не попалась подделка.
Когда пользователь открывает приложение, система опознает его: если в Google-таблицах Access не равен Yes и нет отметки trial, то подписка неактивна и выводится экран оплаты. После оплаты подписки в Integromat запускаются две цепочки: первая — для уже зарегистрированных пользователей.
Если пользователь оплатил подписку с помощью банковской карты, информация о ней сохраняется. За 3 дня до окончания подписки пользователя предупредят о предстоящем списании. После автооплаты система пришлет результат — успешно прошло списание или денег на карте не оказалось. Эта логика собрана на GetCourse.
Ornum. мобильное приложение для геймификации обучения и личного развития
- Платформа: Bubble
- Время на разработку: 3 недели
- Затраты: бесплатный тариф Bubble
Еще год назад Евгений управлял SMM-агентством и не думал, что скоро будет собирать полноценные мобильные приложения. Вместе с WeLoveNoCode он сделал Ornum — приложение для управления карьерой.
Само приложение Евгений собрал в одиночку — 2−3 недели, после этого его упаковали в специальные контейнеры, чтобы загрузить в App Store и Google Play. На сегодняшний день это самое крутое мобильное приложение на Bubble от российских разработчиков, которое мы встречали.
В Ornum реализована сложная и интересная бизнес-логика: большая база данных с 49 скилами и связями между ними, настроена система управления краткосрочными и долгосрочными целями, есть механика ежедневных заданий, а также красивые дашборды и дорожные карты навыков — всё это генерируется автоматически и сделано без кода.
Progressdialog
Как ты помнишь из многих наших статей про Android, обмен данными по сети — всегда долгий процесс, выполняющийся в отдельном потоке. Если никак не показывать пользователю, что приложение работает, он может подумать, будто что-то пошло не так. Для таких случаев есть класс ProgressDialog — элемент с анимированным крутящимся индикатором, демонстрирующий пользователю, что приложение чем-то занято.
mProgressDialog = new ProgressDialog(this);
mProgressDialog.setMessage(getString(R.string.loading));
mProgressDialog.setIndeterminate(true);
mProgressDialog.show();
Его будет логично использовать при старте Activity, если приложению придется запрашивать у AS токен доступа. Когда вычисления закончатся, убрать этот элемент можно, вызвав метод hide.
if (opr.isDone()) {
...
} else {
showProgressDialog();
...
}
mProgressDialog.hide();
Redux/reducers/index.js
import { combineReducers } from 'redux';
import counterReducer from './counterReducer';
export default combineReducers({
counter: counterReducer
});
Из документации redux-dev-tools следует, что нам необходимо внести изменения в configureStore. Вспомним, что инструменты redux-dev-tools нам нужны только для разработки, поэтому повторим маневр, описанный ранее:
- переименуем configureStore.js в configureStore.prod.js;
- реализуем configureStore.dev.js;
- реализуем configureStore.js, который в зависимости от системного ландшафта использует либо configureStore.prod.js, либо configureStore.dev.js.
mv redux/configureStore.js redux/configureStore.prod.js
Signinbutton
Чтобы дизайнеры всего мира не мучились, копируя логотип Google, в Google Play Services есть готовая реализация кнопки со знакомым каждому пользователю Android интерфейсом. Без лишних сомнений добавляем ее в верстку UI.
Silientsignin
Чтобы пользователю не приходилось снова и снова вводить свои учетные данные, в GSI доступна возможность тихого входа. Для этого нам понадобится метод onStart. При тихом входе проверяется состояние токена доступа к PR. Если пользователь недавно из нашего приложения входил в свой аккаунт, то все хорошо и приложение сразу же получит все нужные данные. В противном случае начнется обмен данными с AS и приложение с небольшой задержкой получит новый токен.
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
GoogleSignInResult result = opr.get();
handleSignInResult(result);
} else {
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
@Override
public void onResult(GoogleSignInResult googleSignInResult) {
handleSignInResult(googleSignInResult);
...
}
}
}
Src/client.js
const initialState = window.REDUX_INITIAL_STATE || {};
--- const store = configureStore();
const store = configureStore(initialState);
Как видно из кода, глобальное состояние я передаю в переменной REDUX_INITIAL_STATE.
Src/components/authbuttons/index.js
import OAuthButton from './OAuthButton';
import SignOutButton from './SignOutButton';
export { OAuthButton, SignOutButton };
Я добавлю авторизацию на страницу HelloWorldPage.
Src/components/helloworldpage/helloworldpage.jsx
import { OAuthButton, SignOutButton } from 'components/AuthButtons';
<h2>Авторизация</h2>
<OAuthButton provider='github' />
<SignOutButton />
Настало время насладиться результатами нашего труда. Нажимаем на кнопку “Войти”, используем свой github аккаунт для авторизации и… мы в системе! Кнопка “Войти” исчезла, зато появилась кнопка “Выйти”. Проверим, что сессия сохраняется, для этого перезагрузим страницу.
Кнопка “Выйти” не исчезла, а в redux-dev-tools можно найти информацию о пользователе. Отлично! Пока все работает. Переходим на страницу “Время”, нажимаем на кнопку “Запросить” и видим, что timestamp отобразился — это сервер вернул нам данные.
На этом можно было бы закончить, но нам нужно “отшлифовать” наше приложение.
Src/components/timepage/timepage.jsx
import React, { Component, PropTypes } from 'react';
import { connect } from 'react-redux';
import PageHeader from 'react-bootstrap/lib/PageHeader';
import Button from 'react-bootstrap-button-loader';
import { timeRequest } from 'redux/actions/timeActions';
const propTypes = {
dispatch: PropTypes.func.isRequired
};
class TimePage extends Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.props.dispatch(timeRequest());
}
render() {
return (
<div>
<PageHeader>Timestamp</PageHeader>
<Button onClick={this.handleClick}>Запросить!</Button>
</div>
);
}
}
TimePage.propTypes = propTypes;
export default connect()(TimePage);
Заметим, что нам пришлось использовать connect из react-redux, чтобы у нашей кнопки был доступ к функции dispatch для изменения глобального состояния.
Самое время посмотреть на результаты трудов: откроем страницу “Время” в браузере, нажмем на кнопку “Запросить”. Интерфейс пока еще ничего не делает, но в redux-dev-tools мы теперь видим, как запускаются actions, которые мы совсем недавно реализовали.
Настало время оживить интерфейс. Начнем с реализации логики для обновления глобального состояния
Src/redux/configurestore.dev.js
import { applyMiddleware, createStore, compose } from 'redux';
import thunk from 'redux-thunk';
import DevTools from 'components/DevTools';
import rootReducer from './reducers';
export default function (initialState = {}) {
const store = createStore(rootReducer, initialState, compose(
applyMiddleware(thunk),
DevTools.instrument()
)
);
if (module.hot) {
module.hot.accept('./reducers', () =>
store.replaceReducer(require('./reducers').default)
);
}
return store;
}
Точка входа configureStore
Src/redux/configurestore.js
if (process.env.NODE_ENV === 'production') {
module.exports = require('./configureStore.prod');
} else {
module.exports = require('./configureStore.dev');
}
Все готово! Перезапускаем webpack-dev-server и nodemon, открываем браузер и видим, что справа появилась панель, которая отражает содержимое глобального состояния. Теперь откроем страницу со счетчиками и понажимаем на ReduxCounter.
Одновременно с каждым кликом мы видим, как в очередь redux поступают действия и глобальное состояние изменяется. Нажав на Revert, мы сможем отменить последнее действие, а нажав на Commit — утвердить все действия и очистить текущую очередь команд.
Примечание: после добавления redux-dev-tools, возможно, вы увидите сообщение в консоли: “React attempted to reuse markup in a container but the checksum was invalid…”. Это означает, что серверная и клиентская часть приложения рендерят неодинаковый контент.
Это очень плохо, и в своих приложениях таких ситуаций следует избегать. Однако, в данном случае виновником является redux-dev-tools, который мы все равно в продуктиве использовать не будем, поэтому можно сделать исключение и спокойно проигнорировать сообщение о проблеме.
Update: спасибо пользователям gialdeyn и Lerayne, починить SSR с redux-dev-tools можно следующим способом
Src/redux/configurestore.prod.js
import { applyMiddleware, createStore } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';
export default function (initialState = {}) {
return createStore(rootReducer, initialState, applyMiddleware(thunk));
}
Реализация configureStore.dev.js с DevTools и поддержкой hot-reload.
Src/redux/reducers/index.js
import timeReducer from './timeReducer';
export default combineReducers({
counter: counterReducer,
time: timeReducer
});
Снова откроем браузер и, предварительно очистив очередь redux-dev-tools, покликаем по кнопке “Запросить”. Интерфейс все еще не обновляется, но теперь наши actions изменяют глобальное состояние согласно коду нашего редьюсера, а это значит, что “под капотом” вся логика работает как надо. Дело за малым — “оживим” интерфейс.
Src/redux/reducers/timereducer.js
import { TIME_REQUEST_STARTED, TIME_REQUEST_FINISHED, TIME_REQUEST_ERROR } from 'redux/actions/timeActions';
const initialState = {
time: null,
errors: null,
loading: false
};
export default function (state = initialState, action) {
switch (action.type) {
case TIME_REQUEST_STARTED:
return Object.assign({}, state, { loading: true, errors: null });
case TIME_REQUEST_FINISHED:
return {
loading: false,
errors: null,
time: action.time
};
case TIME_REQUEST_ERROR:
return Object.assign({}, state, { loading: false, errors: action.errors });
default:
return state;
}
}
Важный момент, о котором нельзя забывать: по спецификации redux мы не имеем права изменять переданное нам состояние и обязаны возвращать либо его же, либо новый объект. Для формирования нового объекта я использую Object.assign, который берет исходный объект и применяет к нему нужные мне изменения.
Хорошо, теперь добавим новый редьюсер в корневой редьюсер.
Wom. airbnb для аренды квартир в вене
- Платформа: Glide
- Время на разработку: 70 часов
- Затраты: 12$ (базовый тариф в Glide)
Путешествуя по Вене, digital-стратег Олег Ширяев обнаружил, что арендовать на короткий срок квартиру в центре города практически невозможно. Если и удавалось найти вариант, то квартира была едва пригодна для жилья. Все объекты контролировались риэлторами и разного рода посредниками.
Когда Олег нашел способ арендовать жилье без посредников, к нему пришла идея мобильного приложения.
WOM — это площадка, которая помогает найти и забронировать жилье в центре Вены напрямую у владельца. Если уже пользовались Airbnb, то и в WOM разберетесь. У приложений даже схожие интерфейсы — чтобы пользователи не переучивались и заново не привыкали к новому меню.
Сервис разработан на Glide — платформе мобильной none-code разработки. Через веб-интерфейс разработчик редактирует визуал, а с данными работает в подключенной Google-таблице, которая выполняет роль базы данных. Создатели Glide говорят, что простейшие приложения можно собрать за 7(!) секунд.
WOM получился полноценной площадкой с каталогом квартир, картой, разделами «Вам может быть интересно» и «Сейчас просматривают». Олегу понадобилась ночь на изучение интерфейса Gilde и 2-3 дня на создание экранов и заполнение базы данных.
WOM хоть и разрабатывалось любителем, но выглядит красиво и современно. Это еще одно достоинство none-code приложений: разработчик имеет дело только с готовыми шаблонами. Поэтому получается профессиональный дизайн без услуг художника. Сейчас актуальная версия на немецком тестируется в Вене.
Авторизация
Нам осталось добавить авторизацию в написанное приложение Bookstore. Откроем проект из предыдущей части и добавим в зависимости библиотеку для работы с JWT (как было показано выше). Также добавим две новых настройки в application.properties:
auth.enabled=true
auth.jwt.secret=30faa058f27f690c7e9a098d54ebcfb3d8725bcb85ee7907a2d84c69622229e2
Одна из них вам уже знакома, а вторую будем использовать для включение/отключения авторизации.
Для проверки полученного токена опишем интерфейс TokenService и его реализацию:
public interface TokenService {
boolean checkToken(String token);
}
@Service
@Slf4j
public class DefaultTokenService implements TokenService {
@Value("${auth.jwt.secret}")
private String secretKey;
@Override
public boolean checkToken(String token) {
Algorithm algorithm = Algorithm.HMAC256(secretKey);
JWTVerifier verifier = JWT.require(algorithm).build();
try {
DecodedJWT decodedJWT = verifier.verify(token);
if (!decodedJWT.getIssuer().equals("auth-service")) {
log.error("Issuer is incorrect");
return false;
}
if (!decodedJWT.getAudience().contains("bookstore")) {
log.error("Audience is incorrect");
return false;
}
} catch (JWTVerificationException e) {
log.error("Token is invalid: " e.getMessage());
return false;
}
return true;
}
}
Здесь мы берем значение секретного ключа из настроек приложения, сверяем подпись и проводим все необходимые проверки.
Закроем все эндпоинты и разрешим доступ только при наличии авторизационного токена. Значение авторизационного токена необходимо положить в заголовок следующим образом:
Authorization: Bearer <значение токена>
Осталось написать фильтр, который будет осуществлять чтение заголовка и принимать решение об авторизации запроса:
Библиотека gsi
OAuth работает практически из коробки. К примеру, Google реализовала этот механизм в подключаемой библиотеке. В данном случае этот механизм называется Google Sign-In (GSI), и реализован он в библиотеке Google Play Services. Для ее подключения необходимо изменить оба Gradle-файла, но мы уже не раз пользовались этой библиотекой, поэтому трудностей у тебя возникнуть не должно.
classpath 'com.google.gms:google-services:2.0.0-alpha6'
apply plugin: 'com.google.gms.google-services'
compile 'com.google.android.gms:play-services-auth:9.0.2'
Генерация ключа
Как и при работе с любым другим API из библиотеки Google Play Services, необходимо добавить в приложение конфигурационный файл, созданный на сайте Google. Обрати внимание, что в этот раз он будет жестко привязан к цифровой подписи устройства, на котором разрабатывается приложение. Поэтому если потом проект с созданным конфигом собрать на другом компьютере, то GSI работать не станет.
Интегрируемся
Поскольку GSI будет сам отрисовывать интерфейс аутентификации пользователя, для его реализации целесообразно выделить класс — наследник компонента Activity. Начнем с класса GoogleApiСlient, объект которого должен быть создан раньше всех. Это базовый класс для работы с любыми функциями из Google Play Services. Когда все действия совершаются в Activity, то это подключение удобнее реализовать в методе onCreate.
protected void onCreate(Bundle savedInstanceState) {
mGoogleApiClient = new GoogleApiClient.Builder(this)
.enableAutoManage(this, this)
.addApi(Auth.GOOGLE_SIGN_IN_API, gso)
.build();
Объект собирается с помощью сборщика GoogleApiClient.Builder, затем он самостоятельно подключается к серверу Google (enableAutoManage) и реализует API, в данном случае это GSI (константа GOOGLE_SIGN_IN_API).
Когда пользователь будет вводить пароль к своему аккаунту, Google еще раз спросит, точно ли приложению они нужны. Данные, которые запрашивает приложение, задаются заранее объектом gso — класс GoogleSignInOptions. Если приложению будут нужны email и данные из профиля пользователя, то объект собирается билдером вот с такими параметрами.
GoogleSignInOptions gso = new GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN)
.requestEmail().requestProfile().build();
Какими бывают мобильные приложения
Существует три подхода к созданию мобильных приложений:
- Традиционный. Предполагают написание кода, создание макета, оптимизацию, команду и другие этапы.
- Зерокодинг. Не требует знания языков программирования. Это похоже на использование Тильды (это тоже, кстати, инструмент зерокодеров) для создания сайтов: не надо знать CSS, HTML, JS — просто расставляешь блоки с контентом, настраиваешь анимацию и получаешь отлчиный сайт.
- Low-code — это что-то среднее между зерокодингом и программированием: писать код все-таки приходится, но немного.
Инструменты зерокодинга уже могут покрыть большинство задач бизнеса. И вместо того, чтобы изобретать велосипед, можно за несколько дней запустить MVP или проверить гипотезу с минимальными затратами. А если в запасе хотя бы пара недель — то и запилить полноценное приложение.
При этом обычная разработка затянется на 3−6 месяцев и съест до миллиона рублей — если работать с фрилансером или скромной региональной студией.
На курсе «Зерокодер мобильных приложений» ты научишься создавать приложения под iOS и Android. Простые — за 1−2 дня, сложные — за 1−2 недели. Курс состоит из 5-и модулей, 30 уроков, тренировочных задач и Q&A-сессий с лучшими экспертами в Glide и Adalo.
Ложка дегтя
Прибегнуть к сторонней базе данных пользователей легко и удобно, но разработчики все равно оставляют пользователю возможность создать новый аккаунт в приложении. И правильно делают, ведь у OAuth есть несколько слабых мест, которые могут быть не так очевидны.
- 100%-я интернет-зависимость. При создании Activity объект
GoogleApiClient
сразу же начинает обмен данными с AS — очевидно, если у пользователя не будет доступа к сети, он не сможет зайти в свою учетную запись. Не забываем и про возможную сегментацию: в мире много мест, где есть интернет, но нет Google. - Правила игры могут поменяться. Сколько раз уже случалось, что популярный сервис или API внезапно исчезал или для него менялись правила доступа. К примеру, сервис GCM, о котором мы недавно писали, похоже, может не дожить до конца года: Google просит разработчиков переходить на технологию Firebase Cloud Messaging.
- У приложения нет своих пользователей. С OAuth в чистом виде разработчик теряет представление о том, кто же вообще интересуется его программой. Конечно, можно после успешной авторизации дублировать данные на свой сервер, но они будут неполными и не всегда актуальными.
Мобильный зерокодинг и традиционная разработка: стоимость и сроки
Разработка приложения «под ключ» — сложный процесс, в котором участвует целая команда специалистов. Программисты пишут бэкенд и фронтенд, дизайнеры создают «человеческий» UX/UI и вкусную картинку, тестировщики ищут ошибки, проджекты управляют всем процессом, лиды — командами, эккаунты общаются с клиентами. И каждый не просто просиживает штаны, а действительно работает и нужен.
Сколько денег возьмет за разработку веб-студия и сколько времени потратит, зависит от сложности проекта и имиджа компании, но в среднем — от 500 тыс. до 5 млн рублей, а средний срок разработки — 4−6 месяцев (по сведениям с Хабра, DTF и Appinventive).
Сложные приложения легко могут стоить дороже 10 млн рублей и пилиться больше года — особенно если поджимают сроки или подрядчик входит в какой-то рейтинг вроде Теглайна. И всё это без учёта поддержки, обновлений, продвижения и возможных проблем с масштабированием и доработками.
Nocode-разработка обходится дешевле. Например, Сергей Горелов в одиночку собрал полнофункциональное приложение для фитнес-клуба за пару недель — такое же приложение обычная студия будет разрабатывать около полугода и возьмёт за работу 700−800 тысяч рублей.
Чтобы быть в курсе новостей зерокодинга, присоединяйтесь к телеграм-каналу «Зерокодер», чату «Я — зекрокодер» и комьюнити инструментов: Glide, Adalo, Bubble.
А Евгений Спорыхин из nocode Hero вместе с WeLovEnocode запилил карьерный трекер с геймификацией на Bubble. Вместе с детализацией техзадания, доработками, дополнительными функциями и пятью итерациями по дизайну (клиент не совсем понимал, какой он хочет видеть визуальную составляющую) это заняло три месяца и обошлось заказчику примерно в 700 тысяч рублей.
Аналогичная работа «в коде» длилась бы гораздо дольше, а заказчик отдал бы не меньше 4 млн рублей. При этом первую полнофункциональную версию запустили уже через 2 недели — всё остальное ушло на доработки дизайна и добавление новых идей клиента.
Да, у мобильных приложений на зерокодинге пока есть некоторые ограничения: например, чтобы сделать массовый сервис с трафиком в десятки миллионов человек, когда критичны скорость работы и премиальный дизайн, придется создавать свое решение, нанимать программистов или отдавать разработку на аутсорс.
Снижение стоимости мобильных приложений неизбежно привлечет массовую категорию новых клиентов — теперь приложение может себе позволить даже скромный ИП-шник или владелец пары овощных киосков.
На чем собирают мобильные приложения без кода
Самые мощные и популярные инструменты мобильной разработки без кода — Adalo, Glide и Bubble. С их помощью можно создать и опубликовать мобильное приложение. Они бывают трех типов:
Мобильная версия сайта — сайт в интернете, адаптированный под экраны смартфонов. Это самый «бесправный» тип приложений: всегда нужен интернет, пуши можно включать только в браузере, постоянно на виду элементы навигации браузера.
PWA (Progressive Web Application) — когда мобильная версия сайта устанавливается на смартфон как приложение. Из плюсов — не надо поддерживать две кодовые базы, под iOS и Android, приложение всегда «обновлено» до последней версии, можно работать с некоторыми нативными функциями смартфонов.
Нативные — когда приложение публикуется в официальных сторах. В Adalo уже встроена такая функция, а приложения на Bubble можно обернуть в специальный контейнер и тоже опубликовать в Google Play и App Store. Нативные приложения позволяют работать со всеми функциями телефона: камерой, микрофоном, GPS, контактами, файлами, акселерометром, push-уведомлениями, памятью девайса, адаптивной версткой — всё, как в обычном коде, только без кода.
Проекция. онлайн-фотошкола с элементами соцсети и админкой
- Платформа: Glide
- Время на разработку: 3 недели
- Затраты: 12$ по базовому тарифу
Еще один пример удачного приложения, собранного без кода — обучающая платформа «Проекция». Ее разработал Илья Ткач для сообщества фотографов «Фотодепартамента».
«Проекция» — приложение для учеников одноименной онлайн-фотошколы. В нем ученики делают домашние задания, получают оценки и общаются друг с другом в тематических чатах. Администраторы приложения размещают обучающие материалы и упражнения через панель управления. Можно оформить подписку и получить доступ к закрытым материалам и курсам.
Есть задания в виде тестов (чек-листы) и такие, к которым нужно приложить фото или написать развернутый ответ. Преподаватель видит результаты и выставляет оценки. Учеников, которые сделали задание лучше других, можно хвалить «знаком отличника».
Благодаря приложению и сообществу ученики сильнее погружаются в обучение, а преподавателям проще размещать материалы и общаться с учениками. Каталог учеников и чаты помогают фотографам нарабатывать профессиональные связи.
Реализация
Теоретическая часть закончена, запускаем Android Studio. OAuth сейчас очень популярна, на сайте проекта указано больше десяти крупных проектов, поддерживающих эту технологию. Как ты уже мог догадаться, общий принцип работы в каждом случае одинаков, различаются только названия классов и адреса удостоверяющих центров.
Поскольку мы пишем под Android, логично будет включить в наше приложение авторизацию через аккаунты Google — почти у каждого пользователя этой ОС есть такая учетная запись. Я знаю только одного человека, который не пользуется Google Play, но он параноик, а OAuth вообще не для них :).
Революция в мобильной разработке
Зерокодинг — отличный способ создать настоящее мобильное приложение, которое по визуалу и функциям не уступает традиционным решениям, но выигрывает в скорости и стоимости.
В мобильной разработке сейчас происходит то же, что и в создании сайтов в 2022−2022 годах. Технологии быстро развиваются и становятся доступными — это даёт хороший запас маржинальности в коммерческих и личных проектах. Gartner прогнозирует, что к 2024 году 65% разработки всех приложений перейдет на no- и low-code — так что прямо сейчас мы наблюдаем революцию в разработке.
Те, кто поверил в новые технологии, уже сейчас зарабатывают на мобильной разработке без кода от 300 тыс. руб. в месяц на 2−3 проектах. Это золотое время — и оно скоро может закончиться. Сейчас один человек может составить конкуренцию студиям мобильной разработки со штатом программистов и дизайнеров: nocode-разработка занимает меньше времени, а себестоимость проекта снижается до 50 раз.
Конечно, собирать серьезные приложения уровня Сбера или Яндекс Go на зерокодинге не стоит, а вот для создания приложений в малом и среднем бизнесе или MVP для стартапов и даже технологических гигантов это отличный подход.
Изучить Adalo и Glide и сделать свое мобильное приложение без кода можно на нашем курсе «Зерокодер мобильных приложений».
Результат
Нажмите F5, чтобы запустить программу. Если соединение с базой данных успешно установится, вы увидите только что созданную форму.
Исходный код этой простой формы авторизации вы найдете на Github.
***
У нас есть много полезных материалов по C# для начинающих:
Создаем приложение для android быстро и просто
Сегодня я хотел бы поделиться с Вами, как быстро и просто можно создать приложение для Android с базовыми знаниями HTML CSS и JS. По данному примеру код на Java для Android будет минимальным. Благодаря платформе XAMARIN приложения для мобильных телефонов можно делать в Visual Studio.
▍Шаг 1 – Переходим на сайт и Скачиваем бесплатную версию Community.
▍Шаг 2 – Запускаем установку и выбираем параметры. Нас интересует XAMARIN. Но Вы также можете выбрать другие параметры.
После успешной установки мы можем создать свой первый проект.
▍Шаг 3 – Запускаем Visual Studio. Создать проект. В фильтре пишем xamarin, платформа Android, язык c# (Если желаете другой язык можете его выбрать)
▍Шаг 4 – Далее. Указываете имя для своего приложения, выбираете каталог где его сохранить. Создать.
▍Шаг 5 – Указываем пустое приложение и выбираем минимальную версию андроида для запуска этого приложения.
▍Шаг 6 – Жмем ок. Visual Studio автоматически создает код для приложения
Мы можем его запустить в эмуляторе, который идет комплекте с Visual Studio нажав клавишу F5.
▍Шаг 7 – Теперь немного модифицируем код. В данном случае мы вообще не будем использовать Java. Так как мы будем кодить на C#.
Приводим код к такому виду. Здесь мы создаем WebView контейнер который будет грузить локальный HTML файл, который находится в проекте в папке Assets.
public class MainActivity : AppCompatActivity
{
WebView mWebview; //это контейнер для просмотра HTML
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
mWebview = new WebView(this);
mWebview.Settings.JavaScriptEnabled = true; //это разрешение работа JS скриптов
mWebview.Settings.DomStorageEnabled = true; //это разрешение на запись в память браузера
mWebview.Settings.BuiltInZoomControls = true; //это разрешение на масштабирование пальцами щипком
mWebview.Settings.DisplayZoomControls = false; //это запрет вывода кнопок масштаба
mWebview.Settings.CacheMode = CacheModes.NoCache; //это отключает либо включает кэширование данных
mWebview.LoadUrl($"file:///android_asset/Content/login.html"); //это загрузка локального файла из папки Asset/Content
SetContentView(mWebview);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
▍Шаг 8 –
Создадим там папку Content.
▍Шаг 9 – Добавим в папку Content файл login.html
▍Шаг 10 – Далее уже пишем на привычном нам HTML CSS JS. Можем нажать на F5 и увидеть результат нашей работы.
По такому принципу можно создать приложение быстро и просто. Файлы html будут выглядеть одинаково на всех устройствах. То есть, Вы можете сделать приложения для Android и iOS с одинаковым интерфейсом. Не надо изучать сложные языки разметки, не надо изучать сложные макеты (сториборды) на iOS. Все можно сделать на HTML.
В идеале, вместо локальных файлов можно сделать загрузку со стороннего сайта. В этом случае Вы можете менять контент приложения без его обновления в AppStore и Google Play.
Q: Но как быть с функциями самой платформы? Пуш сообщения? Как взаимодействовать с самой платформой?
Все очень просто! JavaScript можно использовать для вызова функций Android:
▍Шаг 1 – Немного модифицируем наш файл MainActivity
//добавляем интерфейс для javascript
mWebview.AddJavascriptInterface(new JavaScriptInterface(), "interface");
//
▍Шаг 2 –
Далее создаем класс JavaScriptInterface на который будет ругаться Visual Studio
public class JavaScriptInterface : Java.Lang.Object
{
[JavascriptInterface]
[Export("alert")] //здесь мы указываем название функции вызываемой из html файла interface.alert('сообщение пользователю');
public void alert(string data)
{
Toast.MakeText(Application.Context, data, ToastLength.Short).Show();//здесь Андроид выведет сообщение посредством Toast
}
}
Мы видим, что теперь программа ругается на Export так как не знает что это такое.
▍Шаг 3 – Добавим нужную библиотеку
▍Шаг 4 – В фильтре напишем mono
▍Шаг 5 – Найдем Export и поставим галочку
▍Шаг 6 – Жмем ок и видим что ошибка пропала.
Так вы можете подключать библиотеки если вдруг Visual Studio ругается на что то.
Toast.MakeText(Application.Context, data, ToastLength.Short).Show();
Данная функция это показ всплывающей информации на экране. Она выполняется именно на платформе Андроида. То есть мы можем написать в HTML файле вызов функции Андроида. Получается полное дружелюбие двух платформ по JavaScript интерфейсу. Данные можно передавать туда сюда. Вызывать переход от одной активити в другую. Все через HTML JavaScript.
Немного модифицируем файл login.htm:
<html>
<head>
<style>
h1 {
color: yellowgreen;
}
</style>
</head>
<body>
<h1>Привет мир</h1>
<button onclick="sendToAndroid();">Нажми меня</button>
<script>
function sendToAndroid() {
//здесь мы запускаем функцию андроида из HTML файла по javacsript интерфейсу
interface.alert("текст сообщения");
}
</script>
</body>
</html>
жмем F5
Теперь при нажатии на кнопку HTML вызывается функция Toast андроида и выводиться сообщение пользователю.
Спасибо за внимание.
P.s. Полный листинг MainActivity
using Android.App;
using Android.OS;
using Android.Runtime;
using Android.Webkit;
using Android.Widget;
using AndroidX.AppCompat.App;
using Java.Interop;
namespace MyFirstApp
{
[Activity(Label = "@string/app_name", Theme = "@style/AppTheme", MainLauncher = true)]
public class MainActivity : AppCompatActivity
{
WebView mWebview; //это контейнер для просмотра HTML
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
Xamarin.Essentials.Platform.Init(this, savedInstanceState);
mWebview = new WebView(this);
mWebview.Settings.JavaScriptEnabled = true; //это разрешение работа JS скриптов
mWebview.Settings.DomStorageEnabled = true; //это разрешение на запись в память браузера
mWebview.Settings.BuiltInZoomControls = true; //это разрешение на масштабирование пальцами щипком
mWebview.Settings.DisplayZoomControls = false; //это запрет вывода кнопок масштаба
mWebview.Settings.CacheMode = CacheModes.NoCache; //это отключает либо включает кэширование данных
//добавляем интерфейс для javascript
mWebview.AddJavascriptInterface(new JavaScriptInterface(), "interface");
//
mWebview.LoadUrl($"file:///android_asset/Content/login.html"); //это загрузка локального файла из папки Asset/Content
SetContentView(mWebview);
}
public override void OnRequestPermissionsResult(int requestCode, string[] permissions, [GeneratedEnum] Android.Content.PM.Permission[] grantResults)
{
Xamarin.Essentials.Platform.OnRequestPermissionsResult(requestCode, permissions, grantResults);
base.OnRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
public class JavaScriptInterface : Java.Lang.Object
{
[JavascriptInterface]
[Export("alert")]
public void alert(string data)
{
Toast.MakeText(Application.Context, data, ToastLength.Short).Show();
}
}
}
Устанавливаем пакет react-bootstrap-button-loader
npm i --save react-bootstrap-button-loader
Устройство oauth
С появлением технологии OAuth алгоритм регистрации на новом ресурсе для пользователя резко изменился. Теперь не надо заучивать очередные учетные данные, а можно несколькими нажатиями войти на сайт с помощью своей учетной записи одной из социальных сетей.
Если кратко, то «регистрация» на новом ресурсе с помощью OAuth выглядит так: между пользователем и ресурсом появляется посредник — сервер (чаще всего социальной сети), который получает уведомление пользователя о его намерении, а затем подтверждает ресурсу, что он уже знает этого пользователя и готов поделиться с ресурсом его учетными данными.
Шаг 2. создание проекта
Создайте проект для нового приложения. В Visual Studio для этого нужно зайти в меню File > New > Project.
После этого появится окно New Project:
В поле Name нужно вписать название вашего проекта, в поле Location – указать нужную директорию, в Solution name – ввести название решения. Заполнили данные – нажимаем OK.
Шаг 4. настройка соединения с базой
Создайте класс connection для настройки соединения с базой. Пример реализации представлен в листинге ниже:
Шаг 5. код авторизации
Наконец, вернитесь к форме и добавьте следующий код:
Вместо заключения
Вот и подошел к концу цикл статей о веб-приложении на React.js с нуля. Искренне надеюсь, что он был вам полезен!
С удовольствием отвечу на ваши вопросы в комментариях, а также принимаю запросы на темы для следующих статей.