Статьи

Создайте приложение для Android с React Native

В этой статье я покажу вам, как создать приложение Pokedex с React Native в Android. Приложение не будет таким полнофункциональным, как приложение в Google Play Store, только с текстовым полем для ввода точного имени покемона, а затем с указанием деталей при отправке. Мы также собираемся добавить функцию преобразования текста в речь, которая будет вслух читать детали. Вот как будет выглядеть финальное приложение:

финальное приложение pokedex

Примечание : я не буду показывать начальную настройку, такую ​​как установка Android SDK или установка React Native и создание проекта или любых других инструментов разработки, которые вам нужны. Я предполагаю, что это будет ваше первое приложение React Native и предоставит подробное объяснение для каждого блока кода.

Сборка сервера

Сначала нам нужно построить серверный компонент для этого приложения. Мы будем использовать PHP в качестве серверного языка и CouchDB для базы данных. Инструкции по установке для CouchDB здесь и для PHP здесь .

После завершения проверьте, работает ли CouchDB, выполнив curl localhost:5984 , это должно вернуть что-то вроде:

 {"couchdb":"Welcome","uuid":"xxxxxxxxxxxxxxxx","version":"1.6.0","vendor":{"name":"Ubuntu","version":"15.10"}} 

Теперь откройте http: // localhost: 5984 / _utils в вашем браузере, чтобы получить доступ к Futon, системе управления CouchDB по умолчанию. Нажмите Создать базу данных, чтобы создать базу данных, в которой мы будем хранить данные, полученные от API Pokemon . Введите pokedex для имени базы данных, затем нажмите « Создать» . Мы можем получить доступ к API напрямую через React Native, но для этого потребуется больше сетевого доступа. Хранение его в базе данных сначала позволяет нам получить все с помощью одного запроса. Это также позволяет нам запрашивать данные любым удобным для нас способом.

С этим из пути, давайте начнем строить.

Сохранение данных

Далее мы переходим к созданию серверного компонента. Начните с создания рабочего каталога внутри папки вашего веб-сервера. Затем в рабочем каталоге создайте файл composer.json и добавьте следующее:

 { "require": { "doctrine/couchdb-odm": "@dev" }, "minimum-stability": "dev", "prefer-stable": true } 

Это определяет библиотеку doctrine/couchdb-odm как зависимость для этого проекта. Эта библиотека позволяет нам работать с CouchDB на PHP. Выполните composer install чтобы установить его.

Во время установки создайте файл pokemon.php в вашем рабочем каталоге и добавьте следующее:

 <?php require 'vendor/autoload.php'; set_time_limit(0); $client = \Doctrine\CouchDB\CouchDBClient::create(array('dbname' => 'pokedex')); $pokedex = file_get_contents('http://pokeapi.co/api/v1/pokedex/1/'); $pokedex_data = json_decode($pokedex, true); foreach($pokedex_data['pokemon'] as $row){ //get details $pokemon = file_get_contents('http://pokeapi.co/' . $row['resource_uri']); $pokemon = json_decode($pokemon, true); //get description $pokemon_description = file_get_contents('http://pokeapi.co/' . $pokemon['descriptions'][0]['resource_uri']); $pokemon['description'] = json_decode($pokemon_description, true)['description']; //get sprites $pokemon_sprites = file_get_contents('http://pokeapi.co' . $pokemon['sprites'][0]['resource_uri']); $pokemon_sprites = json_decode($pokemon_sprites, true); $pokemon['small_photo'] = 'http://pokeapi.co' . $pokemon_sprites['image']; $client->postDocument($pokemon); } 

Разбивая код выше, мы сначала включаем файл автозагрузчика. Это автоматически загрузит все библиотеки, которые мы установили через Composer. Ниже мы устанавливаем ограничение по времени на ноль. PHP-скрипты имеют максимальное время выполнения по умолчанию, и как только оно достигает этого времени, оно прекращает выполнение. Добавление этого снимает этот предел. На момент написания этой статьи насчитывалось 721 Pokemon, и мы должны выполнить в общей сложности три HTTP-запроса для каждого Pokemon, чтобы получить общие сведения, описания и спрайты … Я думаю, вы поняли идею.

 <?php require 'vendor/autoload.php'; set_time_limit(0); 

Инициализируйте клиент CouchDB и укажите имя базы данных, с которой мы будем работать.

 <?php $client = \Doctrine\CouchDB\CouchDBClient::create(array('dbname' => 'pokedex')); 

Получить полный список Pokemon из API можно с помощью функции file_get_contents . Это возвращает данные в формате JSON, поэтому мы должны преобразовать их в массив для работы с данными:

 <?php $pokedex = file_get_contents('http://pokeapi.co/api/v1/pokedex/1/'); $pokedex_data = json_decode($pokedex, true); 

Просмотрите все результаты:

 <?php foreach($pokedex_data['pokemon'] as $row){ ... } 

Внутри цикла получите доступ к resource_uri для каждого покемона и используйте его для создания URL-адреса, который возвращает сведения о покемонах.

 <?php //get details $pokemon = file_get_contents('http://pokeapi.co/' . $row['resource_uri']); $pokemon = json_decode($pokemon, true); 

Используйте данные, полученные из предыдущего запроса, для создания URL-адреса для получения описания покемонов и спрайтов.

 <?php //get description $pokemon_description = file_get_contents('http://pokeapi.co/' . $pokemon['descriptions'][0]['resource_uri']); $pokemon['description'] = json_decode($pokemon_description, true)['description']; //get sprites $pokemon_sprites = file_get_contents('http://pokeapi.co' . $pokemon['sprites'][0]['resource_uri']); $pokemon_sprites = json_decode($pokemon_sprites, true); $pokemon['small_photo'] = 'http://pokeapi.co' . $pokemon_sprites['image']; 

Сохраните данные в CouchDB:

 <?php $client->postDocument($pokemon); 

Чтобы начать сохранение данных, откройте pokemon.php в вашем браузере. Это займет некоторое время, но вы можете перейти к следующему шагу во время его выполнения.

Получение данных

Чтобы получить данные с помощью CouchDB, мы сначала должны создать представление. Чтобы создать представление, перейдите в базу данных, которую мы создали ранее . Нажмите на выпадающий список и выберите временный вид . Добавьте следующий код в текстовое поле функции карты :

 function(doc) { emit(doc.name, null); } 

Нажмите на кнопку «Выполнить», чтобы проверить, что некоторые результаты дают представление.

Посмотреть результаты

Нажмите кнопку « Сохранить как» и введите pokemon для поля проектного документа и by_name для имени представления .

После этого вернитесь в свой рабочий каталог и создайте новый файл с именем get.php и добавьте следующий код:

 <?php require 'vendor/autoload.php'; $client = \Doctrine\CouchDB\CouchDBClient::create(array('dbname' => 'pokedex')); $pokemon = $_GET['name']; $query = $client->createViewQuery('pokemon', 'by_name'); $query->setKey($pokemon); $query->setReduce(false); $query->setIncludeDocs(true); $result = $query->execute(); if(!empty($result[0])){ $data = $result[0]; echo json_encode($data); }else{ $result = array('no_result' => true); echo json_encode($result); } 

Разбивка кода выше. Сначала мы получаем имя покемона, отправленное из приложения.

 <?php $pokemon = $_GET['name']; 

createViewQuery представление, вызвав метод createViewQuery , предоставив имя проектного документа и имя представления, а затем укажите параметры. Здесь мы используем метод setKey для указания запроса, setReduce для дальнейшей фильтрации результатов, возвращаемых из представления, и setIncludeDocs чтобы указать, что фактический документ для каждого результата также возвращается. Вы могли заметить на скриншоте представления результатов ранее, фактического документа там не было. Это потому, что это поведение по умолчанию, вызов setIncludeDocs и предоставление аргумента true будет включать в себя документ, который был сохранен при доступе к файлу pokemon.php ранее.

 <?php $query = $client->createViewQuery('pokemon', 'by_name'); // design document name + view name $query->setKey($pokemon); // set the key to the name of the pokemon $query->setReduce(false); // disable reduce $query->setIncludeDocs(true); // include the actual document for each result $result = $query->execute(); // perform the query 

Затем мы проверяем, есть ли результаты, и возвращаем кодированную версию JSON. В противном случае верните, что результатов нет.

 <?php if(!empty($result[0])){ $data = $result[0]; echo json_encode($data); }else{ $result = array('no_result' => true); echo json_encode($result); } 

На этом этапе, если вы работаете на локальном компьютере, предоставьте сервер общедоступному Интернету с помощью Ngrok . Или используйте внутренний IP-адрес, назначенный вашей сетью. Обратите внимание на это, поскольку мы будем использовать это позже в приложении.

Сборка приложения

Установка зависимостей

Начните с инициализации нового проекта React Native и установки его зависимостей:

 react-native init Pokedex 

После завершения установите зависимости через npm:

 cd Pokedex npm install lodash react-native-android-speech react-native-gifted-spinner --save 

Вот краткое изложение того, что каждый из них делает:

  • lodash : используется для lodash слова в заглавные буквы и извлечения определенных данных из массива.
  • react-native-android-speech : Используется для преобразования текста описания в речь.
  • react-native-gifted-spinner : используется для показа пользователю анимации загрузки во время сетевого запроса.

Вы можете открыть каталог node_modules / реагировать-native / node_modules, чтобы просмотреть различные установленные модули.

После завершения установки посетите репозиторий React Native Android Speech для Github и следуйте инструкциям по установке.

Выполнение сетевых запросов

В корневом каталоге вашего проекта React Native создайте папку src, а внутри нее создайте файл api.js и добавьте следующий код:

 module.exports = function(pokemon){ var url = 'http://192.168.xxx.xxx/pokedex/get.php?name=' + pokemon; return fetch(url).then(function(response){ return response.json(); }).then(function(json){ return json; }); } 

Это экспортирует функцию для извлечения данных с сервера, используя метод fetch который является способом React Native для выполнения сетевых запросов. Этот метод принимает URL-адрес для отправки запроса и возвращает обещание использовать метод then и предоставляет функцию обратного вызова для получения ответа.

Возвращенный ответ не может быть использован напрямую, поэтому мы должны вернуть результат метода json который доступен из объекта response . Оттуда мы можем получить фактические данные JSON, связав другой метод then . Функция обратного вызова будет затем передавать фактические данные JSON в качестве аргумента, который мы затем возвращаем.

Обратите внимание, что мы возвращаем результат метода fetch , это тоже обещание, поэтому, когда мы позже вызываем этот модуль в нашем основном скрипте, нам нужно снова использовать метод then для доступа к данным JSON.

Примечание . Обязательно измените http://192.168.xxx.xxx/ на IP-адрес или доменное имя, указанное ранее. Обратите внимание, что pokedex — это папка внутри моего веб-каталога.

Основной файл приложения

Откройте файл index.android.js . Он должен содержать некоторый код по умолчанию, но давайте его очистим.

Сначала в начале каждого файла мы включим строгий режим. Это помогает предотвратить распространенные ошибки в нашем коде, такие как отсутствие определения переменной перед инициализацией значения.

После этого импортируйте React Native и дополнительные зависимости для проекта.

 'use strict'; var React = require('react-native'); var tts = require('react-native-android-speech') var GiftedSpinner = require('react-native-gifted-spinner'); var _ = require('lodash'); 

Инициализируйте все компоненты и API, которые нам нужны.

 var { AppRegistry, StyleSheet, Text, TextInput, View, Image, ListView } = React; 

React Native имеет различные компоненты и доступные API. По умолчанию они не загружаются, поэтому вам нужно указать, какие вам нужны. Думайте о компонентах как о компонентах пользовательского интерфейса.

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

API React — это способ доступа к различным возможностям устройства, таким как камера и push-уведомления. Хотя вы не можете часто использовать эти API, есть некоторые, которые вы будете использовать в каждом проекте.

Примеры включают таблицу стилей и AppRegistry .

Вот краткое описание для каждого из компонентов и API, которые мы использовали:

  • AppRegistry : для регистрации пользовательских компонентов. В React Native каждая вещь является компонентом, и каждый компонент может состоять из более мелких компонентов.
  • StyleSheet : для объявления стилей, используемых в приложении.
  • Text : для отображения текста.
  • TextInput : для создания текстового поля.
  • View : основной компонент для создания пользовательского интерфейса. В основном используется для упаковки контента.
  • Image : для отображения изображения.
  • ListView : для отображения списка.

Возвращаясь к коду, импортируйте созданный ранее файл src / api.js. Это позволяет нам совершать сетевые вызовы, вызывая метод api .

 var api = require('./src/api.js'); 

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

 var Pokedex = React.createClass({ ... }); 

Внутри класса мы инициализируем состояние. «Состояние» — это способ хранения данных, который будет доступен во всем компоненте React Native. query — это текст, введенный пользователем, hasResult указывает, есть ли результат поиска, noResult указывает, нет ли результата поиска. Это противоположно hasResult но используется для принятия решения о том, отображать ли текст без результатов или нет. Это связано с тем, что при первоначальной загрузке приложения мы не хотим, чтобы пользователь видел этот текст, поскольку он еще даже не начал поиск. result сохраняет текущий результат поиска, полученный с сервера, isLoading указывает, показывает ли загрузчик, а dataSource содержит источник данных для представления списка. Это делается путем создания нового экземпляра ListView.DataSource который принимает объект, содержащий функцию rowHasChanged . Эта функция сообщает ListView о необходимости повторного рендеринга строки при изменении источника данных. В этом случае источником данных является массив объектов типа Pokemon. Вы увидите, как эти данные предоставляются, посмотрев код для метода search .

 getInitialState: function(){ return { query: null, hasResult: false, noResult: false, result: null, isLoading: false, dataSource: new ListView.DataSource({ rowHasChanged: (row1, row2) => row1 !== row2, }) } }, 

Далее мы рассмотрим метод render . Это метод, который отображает интерфейс для нас.

 render: function() { ... }, 

Внутри метода мы возвращаем интерфейс.

 return ( <View style={styles.container}> <View style={styles.search}> <TextInput style={styles.text_input} onChangeText={this.changeText} onSubmitEditing={this.search} placeholder="Type a pokemon name" /> </View> { this.state.hasResult && <View style={styles.result}> <View style={styles.main_details}> <Image source={{uri: this.state.result.small_photo}} style={styles.image_dimensions} resizeMode={Image.resizeMode.contain} /> <Text style={styles.main_text}>{this.state.result.name}</Text> <ListView contentContainerStyle={styles.types} dataSource={this.state.types} renderRow={this.renderType}></ListView> <View style={styles.description}> <Text style={styles.description_text}>{this.state.result.description}</Text> </View> </View> </View> } { this.state.noResult && <View style={styles.no_result}> <Text style={styles.main_text}>Pokemon not found</Text> <Text style={styles.sub_text}>Please type the exact name</Text> </View> } { this.state.isLoading && <View style={styles.loader}> <GiftedSpinner /> </View> } </View> ); 

Разбивка кода выше. У нас есть основной контейнер.

 <View style={styles.container}> </View> 

Примечание : это требование, потому что должен быть только один корневой компонент, в который вложены все остальные компоненты. У него есть атрибут с именем style а значение — это объект, описывающий стиль этого компонента. Позже мы рассмотрим, как объявляется объект styles . А пока просто помните, что вы должны пропускать двойные кавычки при использовании объектов в качестве значений.

Внутри основного контейнера находится компонент для ввода имени покемона. Это имеет три атрибута. onChangeText для указания функции, выполняемой каждый раз, когда изменяется текст внутри текстового поля. onSubmitEditing для указания функции, выполняемой при onSubmitEditing текстового поля. И placeholder для указания текста, который будет отображаться, если в данный момент нет ввода.

 <View style={styles.search}> <TextInput style={styles.text_input} onChangeText={this.changeText} onSubmitEditing={this.search} placeholder="Type a pokemon name" /> </View> 

Далее у нас есть компонент для отображения результатов поиска. Он имеет немного другой синтаксис, чем предыдущий компонент, потому что он заключен в фигурные скобки и в начале есть условие. Это говорит React визуализировать этот компонент, только если в состоянии сохранен результат. Внутри компонента находится компонент Image отображающий фотографию покемона, а под ним компонент Text отображающий имя покемона. После имени ListView отображается тип Pokemon. Некоторые покемоны имеют более одного типа, поэтому нам нужен ListView для его отображения. Наконец, у нас есть другой компонент View который отображает описание.

 { this.state.hasResult && <View style={styles.result}> <View style={styles.main_details}> <Image source={{uri: this.state.result.small_photo}} style={styles.image_dimensions} resizeMode={Image.resizeMode.contain} /> <Text style={styles.main_text}>{this.state.result.name}</Text> <ListView contentContainerStyle={styles.types} dataSource={this.state.types} renderRow={this.renderType}></ListView> <View style={styles.description}> <Text style={styles.description_text}>{this.state.result.description}</Text> </View> </View> </View> } 

Давайте погрузимся в каждый из этих компонентов. Компонент Image принимает атрибут source который позволяет нам указать, откуда исходит изображение. Это может быть сетевой образ (из Интернета), образ файловой системы или из ресурсов приложения. В этом случае мы используем изображение сети, и данные хранятся в состоянии. resizeMode указывает, как изменить размер изображения, когда кадр не соответствует необработанным размерам изображения. Здесь мы использовали в качестве значения. Это означает, что изображение содержится внутри кадра, сохраняя соотношение сторон.

 <Image source={{uri: this.state.result.small_photo}} style={styles.image_dimensions} resizeMode={Image.resizeMode.contain} /> 

Компонент Text отображает текст. Каждый раз, когда вы хотите отобразить текст в React Native, вы всегда должны обернуть его в Text компонент.

 <Text style={styles.main_text}>{this.state.result.name}</Text> 

Компонент ListView отображает список. Здесь есть один недостаток: он принимает contentContainerStyle а не атрибут style чтобы указать стиль. dataSource позволяет нам указать, откуда будут поступать данные, используемые для рендеринга этого списка, а renderRow позволяет нам указать функцию, которая будет выполняться для рендеринга каждого элемента списка.

 <ListView contentContainerStyle={styles.types} dataSource={this.state.types} renderRow={this.renderType}></ListView> 

После отображения результатов поиска у нас есть компонент для отображения, если нет результатов.

 { this.state.noResult && <View style={styles.no_result}> <Text style={styles.main_text}>Pokemon not found</Text> <Text style={styles.sub_text}>Please type the exact name</Text> </View> } 

Ниже у нас есть индикатор загрузки, который использует модуль Gifted Spinner для отображения анимации загрузки. Это отображается только тогда, когда для свойства isLoading в состоянии установлено значение true . Это состояние устанавливается непосредственно перед выполнением сетевого запроса, и оно устанавливается в false когда ответ возвращается.

 { this.state.isLoading && <View style={styles.loader}> <GiftedSpinner /> </View> } 

Далее мы добавляем метод для рендеринга каждого элемента списка. Ранее в объявлении ListView мы указали this.renderType в качестве значения атрибута renderRow , это тот метод.

 renderType: function(type){ return ( <View style={[styles[type.name], styles.type]}> <Text style={styles.type_text}>{type.name}</Text> </View> ); }, 

Если вы проверите код для ListView вы увидите, что нигде в renderRow мы не renderRow type которому мы пытаемся получить доступ ниже. Это потому, что renderRow автоматически передает его за кулисы.

Если вы проверите ответ, возвращенный с сервера, вы увидите, что объект types содержит массив объектов, представляющих каждый тип:

 [ { "name":"electric", "resource_uri":"\/api\/v1\/type\/13\/" } ] 

В методе renderType у нас есть доступ к этому объекту через аргумент type . Мы используем его для отображения имени типа, а также для управления стилем. Позже в нашем объявлении стиля нам нужно добавить разные стили для каждого типа покемонов. Если вы заметили, мы используем две декларации стиля для компонента View . В React Native мы делаем это, добавляя каждое объявление стиля в массив.

 <View style={[styles[type.name], styles.type]}> <Text style={styles.type_text}>{type.name}</Text> </View> 

Затем добавьте метод changeText который выполняется каждый раз, когда изменяется текст в текстовом поле. Фактический текстовый контент передается этому методу, так что это хорошее место для обновления свойства состояния, в котором хранится текущий пользовательский ввод.

 changeText: function(text){ this.setState({ query: text }); }, 

Затем добавьте метод search который выполняется, когда пользователь отправляет текстовое поле. Когда вы набираете текстовое поле в Android, на клавиатуре есть готовая кнопка. Когда вы нажимаете на это, событие onSubmitEditing срабатывает в текстовом поле. Ранее мы указали this.search в качестве значения для атрибута onSubmitEditing поэтому следующий метод будет выполнен, когда это произойдет.

 search: function(){ var pokemon = _.capitalize(this.state.query); this.setState({ isLoading: true }); api(pokemon).then( (data) => { var speech = 'Pokemon was not found. Please type the exact name.'; if(data.doc){ var types = this.state.dataSource.cloneWithRows(data.doc.types); this.setState({ hasResult: true, noResult: false, result: data.doc, types: types, isLoading: false }); var type_names = _.map(data.doc.types, function(type){ return type.name; }); speech = data.doc.name + ". A " + type_names.join(' and ') + ' pokemon. ' + data.doc.description; }else{ this.setState({ hasResult: false, noResult: true, isLoading: false, result: null }); } tts.speak({ text: speech, forceStop : true , language : 'en' }); } ); } 

Разбивка кода выше. Мы вызываем метод capitalize предоставленный Lodash, чтобы преобразовать все символы строки в нижний регистр, а первый символ должен быть в верхнем регистре. Затем мы обновляем состояние, устанавливая для свойства isLoading значение true . Это показывает индикатор загрузки чуть ниже последнего компонента.

 var pokemon = _.capitalize(this.state.query); this.setState({ isLoading: true }); 

Выполните сетевой запрос через модуль api .

 api(pokemon).then( (data) => { ... } ); 

Примечание . Синтаксис функции обратного вызова немного отличается от того, к которому мы привыкли, но почти такой же, как:

 api(pokemon).then(function(data){ ... }); 

Единственное отличие от синтаксиса — это значение объекта this . В более новом синтаксисе this относится к внешней области, а не к функции обратного вызова. Это позволяет нам использовать объект this для обновления состояния внутри функции обратного вызова без необходимости создания переменной, в которой хранится текущая область.

Внутри функции обратного вызова мы устанавливаем текст по умолчанию, используемый для речи.

 var speech = 'Pokemon was not found. Please type the exact name.'; 

Если в результате присутствует объект doc мы сначала извлекаем массив типов и передаем его в качестве аргумента методу cloneWithRows в dataSource инициализированном ранее в состоянии. Это возвращает объект, который можно использовать в качестве значения для атрибута dataSource в <ListView> .

Далее мы обновляем состояние, чтобы результат отображался в пользовательском интерфейсе. Как только это будет сделано, создайте новый массив с именем type_names который будет содержать только имена каждого типа, который имеет Pokemon. Это делается с помощью метода map предоставленного Lodash. Оттуда мы строим текст для преобразования в речь, имя покемона, его типы и его описание.

 if(data.doc){ //create the list view data source var types = this.state.dataSource.cloneWithRows(data.doc.types); //update the state this.setState({ hasResult: true, noResult: false, result: data.doc, types: types, isLoading: false }); //create an array containing the type names var type_names = _.map(data.doc.types, function(type){ return type.name; }); //construct the text to be used for the speech speech = data.doc.name + ". A " + type_names.join(' and ') + ' pokemon. ' + data.doc.description; } 

В противном случае мы устанавливаем все необходимые значения в состоянии. В частности, нам нужно установить для hasResult значение false чтобы компонент результата не отображался, для noResult значение true чтобы он отображал текст без результатов , для isLoading значение false чтобы скрыть индикатор загрузки, и значение null чтобы очистить предыдущий результат. ,

 ... else{ this.setState({ hasResult: false, noResult: true, isLoading: false, result: null }); } 

Прямо под условиями используйте речевой модуль Android для преобразования текста в речь.

 if(data.doc){ ... }else{ ... } tts.speak({ text: speech, forceStop : true , language : 'en' }); 

Затем добавьте стили через API StyleSheet , добавьте их после закрывающих скобок класса Pokodex .

 var styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#FFF' }, search: { flex: 1 }, result: { flex: 8 }, no_result: { flex: 8, alignItems: 'center' }, loader: { flex: 1, alignItems: 'center' }, main_details: { padding: 30, alignItems: 'center' }, image_dimensions: { width: 100, height: 100 }, main_text: { fontSize: 25, fontWeight: 'bold', textAlign: 'center' }, sub_text: { color: '#6e6e6e' }, description: { marginTop: 20 }, text_input: { height: 40, borderColor: 'gray', borderWidth: 1 }, types: { flexDirection: 'row', marginTop: 20 }, type: { padding: 5, width: 100, alignItems: 'center' }, type_text: { color: '#fff', }, normal: { backgroundColor: '#8a8a59' }, fire: { backgroundColor: '#f08030' }, water: { backgroundColor: '#6890f0' }, electric: { backgroundColor: '#f8d030' }, grass: { backgroundColor: '#78c850' }, ice: { backgroundColor: '#98d8d8' }, fighting: { backgroundColor: '#c03028' }, poison: { backgroundColor: '#a040a0' }, ground: { backgroundColor: '#e0c068' }, flying: { backgroundColor: '#a890f0' }, psychic: { backgroundColor: '#f85888' }, bug: { backgroundColor: '#a8b820' }, rock: { backgroundColor: '#b8a038' }, ghost: { backgroundColor: '#705898' }, dragon: { backgroundColor: '#7038f8' }, dark: { backgroundColor: '#705848' }, steel: { backgroundColor: '#b8b8d0' }, fairy: { backgroundColor: '#e898e8' } }); 

Разбивка кода выше. У нас есть основной контейнер, где мы устанавливаем flex в 1 как мы используем Flexbox для разметки. Значение 1 означает, что оно будет занимать весь экран. Это потому, что мы прикрепили этот стиль к корневому компоненту. На этом уровне нет других компонентов, поэтому он будет занимать весь экран.

 container: { flex: 1, backgroundColor: '#FFF' }, 

Далее у нас есть стили для поиска, результата, без результата и загрузчика:

 search: { flex: 1 }, result: { flex: 8 }, no_result: { flex: 8, alignItems: 'center' }, loader: { flex: 1, alignItems: 'center' }, 

Поскольку все они братья и сестры, они делят доступное пространство. Корневой компонент занимает весь экран, поэтому его дочерние элементы также будут использовать весь экран. Думайте с точки зрения фракций. Компоненты поиска и загрузки flex: 1 поэтому они занимают меньше всего места. Как часть, они занимают 1/10 части экрана, поскольку одновременно можно увидеть 10 секций: 1 для поиска, 8 для результата или без результата и 1 для загрузчика.

 <View style={styles.search}> ... </View> <View style={styles.result}> ... </View> <View style={styles.no_result}> ... </View> <View style={styles.loader}> ... </View> 

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

 main_details: { padding: 30, alignItems: 'center' }, 

Далее несколько объявлений стилей, которые являются просто стандартным CSS.

 image_dimensions: { width: 100, height: 100 }, main_text: { fontSize: 25, fontWeight: 'bold', textAlign: 'center' }, sub_text: { color: '#6e6e6e' }, description: { marginTop: 20 }, 

Далее идут стили для списка типов. В ListView установлена row для flexDirection . Это означает, что выделенное ему пространство будет разделяться по строкам. Это позволяет нам достичь эффекта типа float или inline. Это означает, что каждый элемент списка будет отображаться рядом друг с другом. Для каждого элемента списка мы указываем width чтобы ширина была одинаковой независимо от длины текста внутри. padding 5, чтобы текст не был слишком близко к краям контейнера типа. alignItems используется для выравнивания текста по центру.

 types: { flexDirection: 'row', marginTop: 20 }, type: { padding: 5, width: 100, alignItems: 'center', }, type_text: { color: '#fff', }, 

Теперь у нас есть стили для каждого типа покемонов. Например, если Pokemon является электрическим типом, он будет отображать желтоватый цвет фона для контейнера типа.

 normal: { backgroundColor: '#8a8a59' }, fire: { backgroundColor: '#f08030' }, water: { backgroundColor: '#6890f0' }, electric: { backgroundColor: '#f8d030' }, grass: { backgroundColor: '#78c850' }, ice: { backgroundColor: '#98d8d8' }, fighting: { backgroundColor: '#c03028' }, poison: { backgroundColor: '#a040a0' }, ground: { backgroundColor: '#e0c068' }, flying: { backgroundColor: '#a890f0' }, psychic: { backgroundColor: '#f85888' }, bug: { backgroundColor: '#a8b820' }, rock: { backgroundColor: '#b8a038' }, ghost: { backgroundColor: '#705898' }, dragon: { backgroundColor: '#7038f8' }, dark: { backgroundColor: '#705848' }, steel: { backgroundColor: '#b8b8d0' }, fairy: { backgroundColor: '#e898e8' } 

Зарегистрируйте компонент в AppRegistry . Это говорит React Native о рендеринге компонента с именем Pokedex при открытии приложения.

 AppRegistry.registerComponent('Pokedex', () => Pokedex); 

Запуск приложения

Убедитесь, что ваш PHP-бэкэнд работает, а затем запустите react-native run-android чтобы скомпилировать приложение и запустить его на устройстве или в эмуляторе. Вы получите ошибку в этой точке:

ошибка

Это потому, что сервер React должен запускаться первым. Сервер React преобразует компоненты на лету. Когда вы вносите изменения в любой из ваших исходных файлов (например, index.android.js ), он автоматически перекомпилируется. Выполните react-native start с терминала, чтобы запустить сервер React. Вот пример вывода при запуске:

 [7:38:33 AM] <START> Building Dependency Graph [7:38:33 AM] <START> Crawling File System [7:38:33 AM] <START> Loading bundles layout [7:38:33 AM] <END> Loading bundles layout (1ms) React packager ready. [7:38:46 AM] <END> Crawling File System (13516ms) [7:38:46 AM] <START> Building in-memory fs for JavaScript [7:38:52 AM] <END> Building in-memory fs for JavaScript (6200ms) [7:38:52 AM] <START> Building in-memory fs for Assets [7:38:59 AM] <END> Building in-memory fs for Assets (6048ms) [7:38:59 AM] <START> Building Haste Map [7:39:03 AM] <START> Building (deprecated) Asset Map [7:39:05 AM] <END> Building (deprecated) Asset Map (2191ms) [7:39:08 AM] <END> Building Haste Map (9348ms) [7:39:08 AM] <END> Building Dependency Graph (35135ms) 

Когда график зависимостей здания будет завершен, откройте новое окно терминала и выполните adb shell input keyevent 82 чтобы открыть меню разработчика на устройстве. Выберите dev settings из всплывающего меню, затем в разделе отладки введите внутренний IP-адрес вашего компьютера, а затем порт, на котором работает сервер React.

IP-адрес и порт

После завершения вернитесь на главный экран приложения, снова откройте меню разработчика и выберите « Перезагрузить JS», чтобы перезагрузить приложение. На данный момент приложение должно работать.

Общие проблемы

В этом разделе я расскажу о некоторых распространенных проблемах.

Запуск сервера React

Если вы получаете ошибку при запуске сервера React, это обычно из-за сторожа. Вот пример ошибки:

 Error building DependencyGraph: Error: Watcher took too long to load Try running `watchman version` from your terminal https://facebook.github.io/watchman/docs/troubleshooting.html at [object Object]._onTimeout (index.js:103:16) at Timer.listOnTimeout (timers.js:89:15) 

Чтобы исправить, выполните следующее:

 sudo sysctl fs.inotify.max_user_instances=99999 sudo sysctl fs.inotify.max_user_watches=99999 sudo sysctl fs.inotify.max_queued_events=99999 watchman shutdown-server 

Если это не сработает, попробуйте следующее:

 echo 999999 | sudo tee -a /proc/sys/fs/inotify/max_user_instances echo 999999 | sudo tee -a /proc/sys/fs/inotify/max_user_watches echo 999999 | sudo tee -a /proc/sys/fs/inotify/max_queued_events watchman shutdown-server 

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

Проблемы с приложением

После запуска сервера React может возникнуть еще одна проблема: приложение не будет работать после запуска сервера React. Это происходит по двум причинам:

  1. Когда устройство Android и компьютер не находятся в одной сети.
  2. IP-адрес, введенный в устройство, не совпадает с IP-адресом компьютера.

Все, что вам нужно сделать, это убедиться, что это не так, и вы должны быть в порядке.

Отладка и живая перезагрузка

После того, как вы запустите приложение на своем устройстве, вы можете включить некоторые полезные функции, такие как отладка в Chrome и перезагрузка в реальном времени. Вы можете сделать это, открыв меню разработчика и нажав Отладка в Chrome и включив Live Перезагрузить . Это позволяет просматривать выходные данные из console.log и просматривать ошибки на вкладке консоли в Chrome. Live Reload перезагружает приложение, когда вы вносите изменения в исходные файлы.

Куда пойти отсюда

Теперь, когда вы создали свое первое приложение React Native, что дальше? Вот несколько рекомендаций:

Вывод

Из этой статьи вы узнали, как использовать React Native для создания простого приложения Pokedex. Мы рассмотрели основные понятия, такие как создание компонента, таблицы стилей, выполнение сетевых запросов и использование сторонних библиотек. Если у вас возникли проблемы с получением кода для работы, я рекомендую вам ознакомиться с репозиторием Github, в который я загрузил полный исходный код этого проекта.

Если у вас есть какие-либо комментарии или вопросы, пожалуйста, дайте мне знать в комментариях ниже.