Bagian 1Salam!
Hari ini, seperti biasa, mari kita bicara tentang membuat aplikasi seluler dengan kerangka kerja Kivy dan Python. Secara khusus, ini akan fokus pada pembuatan klien seluler untuk satu sumber daya Internet dan menerbitkannya di Google Play. Saya akan memberi tahu Anda masalah apa yang mungkin dimiliki pemula dan pengembang berpengalaman, yang memutuskan untuk mencoba sendiri dalam pengembangan lintas platform dengan Kivy, apa yang dapat dan tidak boleh dilakukan dalam pemrograman dengan Python untuk Android.
Suatu pagi saya menemukan dalam surat saya di Habré surat yang menanyakan apakah saya bisa menggunakan Python dan Kivy untuk "membuat ulang situs svyatye.com dalam aplikasi seluler sehingga orang dapat membaca dan menggunakannya secara offline" dengan publikasi klien berikutnya di Toko aplikasi Google Play. Mengikuti tautan dan menelusuri sumber, yang ternyata menjadi perpustakaan besar kutipan, saya membayangkan bagaimana itu akan terlihat dalam presentasi mobile dan bagaimana saya akan membuat daftar "lebih dari 30.236 ucapan ayah suci dan guru gereja" meskipun panjang kutipan, kadang-kadang , mencapai lebih dari 10.000 karakter (5-6 halaman teks cetak). Karena saya telah bekerja dengan Kivy untuk waktu yang lama, saya segera menyadari bagaimana dan apa yang akan saya lakukan. Karena itu, ia menjawab kepada pelanggan bahwa tidak akan sulit untuk membuat aplikasi seperti itu. Namun, kesulitan, yang akan saya bahas di bawah ini, tetap muncul ...
Tidak ada spesifikasi teknis yang diberikan. Satu-satunya persyaratan adalah aplikasi harus bekerja seperti jam. Tidak ada tenggat waktu. Tidak ada tata letak antarmuka juga. "Semuanya harus sesederhana mungkin, tanpa animasi, transformasi dan sekam lainnya, dengan kata lain, sesederhana mungkin." Yah, semakin baik. Selain itu, keputusan saya sudah matang - aplikasi akan menggunakan satu objek RecycleView, yang akan menampilkan kategori, subkategori, daftar penulis kutipan dan kutipan sendiri.
Daftar
Namun, RecycleView, yang memungkinkan Anda untuk membuka daftar besar ribuan dalam sepersekian detik, berperilaku sangat berbeda dari yang diinginkan. Tidak, tidak ada masalah membuka daftar kutipan, semuanya bekerja dengan cepat, saya bahkan tidak memuat tanda kutip baru dengan jendela "Tunggu" di situs, karena daftar kutipan dari kategori yang dipilih diberikan secara instan dan lengkap. Masalahnya berbeda - pelanggan bersikeras bahwa teks kutipan dalam daftar harus ditampilkan secara keseluruhan dan bahwa RecycleView tidak sepenuhnya sesuai di sini. Faktanya adalah bahwa prinsip pengoperasian widget ini adalah sebagai berikut: satu objek dibuat pada seluruh daftar, yang kemudian hanya dikloning, sebagai akibatnya kita memiliki kecepatan luar biasa dalam membuat daftar, tidak peduli seberapa besar itu. Tetapi ada satu hal - ketinggian item daftar harus diperbaiki dan diketahui sebelumnya. Tetapi jika Anda perlu secara dinamis menghitung ketinggian item daftar berikutnya saat menggulir, seperti dalam kasus saya, maka ada kelambatan yang terlihat - daftar membeku selama sepersekian detik, yang, Anda lihat, sama sekali tidak siap untuk bertindak.
Dengan kesedihan menjadi dua, saya berhasil membujuk pelanggan ke daftar dengan pratinjau kutipan, teks yang akan terbuka sepenuhnya oleh tapu ke teks pratinjau, seperti yang dilakukan di hampir semua forum, bukan karena RecycleView tidak dapat mengatasi tugas, tetapi karena itu adalah yang paling logis: untuk menggulir teks multi-halaman dari kutipan, terutama jika kutipan itu tidak menarik bagi pengguna, dari sudut pandang saya itu tidak benar.
Fig. 1
Pratinjau dan teks lengkap saat merekam pratinjau penawaranOpsi ini bekerja sangat cepat, tetapi ... pelanggan tidak menyukainya ... Saya harus menggunakan ScrollView yang lambat, yang menjadikan daftar SEBELUM itu ditampilkan di layar, yang berarti tidak membekukan pengguliran daftar kutipan, karena menghitung dan merender semua parameter elemen daftar sebelumnya, yang, Tentu, itu akan mempengaruhi kecepatan daftar layar. Biasanya kinerja diletakkan di tempat pertama, dan di sini mereka berkata kepada saya, "biarlah lebih lambat."
Yah, saya tidak berdebat lagi, dan meskipun saya benar-benar tidak menyukai solusi ini, saya harus mentransfer semuanya ke ScrollView. Karena, seperti yang saya katakan, ScrollView sangat lambat, diputuskan untuk menampilkan penawaran dalam sepuluh bagian masing-masing dengan pemuatan otomatis lebih lanjut dari sepuluh berikutnya.
Berjalan sedikit di depan, saya akan mengatakan bahwa ketika umpan balik pertama dari pengguna datang dengan permintaan, mereka mengatakan bahwa bookmark tidak akan benar-benar sakit, karena bagi saya, pelanggan masih meragukan keputusan untuk menggunakan ScrollView, seolah-olah kita meninggalkan preview dari kutipan dan RecycleView, kemudian tanpa masalah mereka bisa langsung mengembalikan dari bookmark yang sebelumnya dilihat oleh pengguna dalam daftar kutipan sesi sebelumnya, tidak peduli berapa lama mereka. Dan dengan ScrollView, pengguna hanya akan menjadi tua sambil menunggu daftar yang akan ditampilkan setidaknya dari semangat kutipan.
Buildozer dan layanan
Ketika aplikasi dikembangkan, ada proposal untuk mengajukan layanan di dalamnya yang akan mengirimkan kutipan acak dari database ke pengguna sekali sehari. Saya belum pernah menangani tugas serupa di Kivy sebelumnya, tetapi mengingat bahwa ada
artikel tentang Habré tentang masalah ini, saya memutuskan untuk mencobanya.
Setelah membunuh satu minggu penuh, memecahkan lima keyboard dan dua monitor, saya tidak dapat mengkompilasi paket sesuai dengan instruksi dari artikel di atas - selama kompilasi kelas yang diperlukan tidak ditemukan. Setelah menulis kepada penulis artikel, saya menyarankan bahwa, ternyata, hanya dua alasan mengapa saya gagal adalah benar: entah saya idiot atau pengembang merusak Buildozer, alat untuk membangun paket APK untuk Android. Asumsi saya ternyata benar - "Tentu saja, mereka melanggarnya, setelah versi 0.33 apa yang akan mereka kumpulkan."
Ya, bagian terbesar dari pertanyaan di
forum Kivy terhubung dengan berbagai masalah yang muncul justru dengan Buildozer. Sekarang setiap versi alat ini membutuhkan versi Cython sendiri, yang akan Anda pilih secara eksperimental untuk waktu yang lama, menggunakan versi Buildozer terbaru Anda tidak akan dapat menambahkan perpustakaan ke proyek JAR Anda, karena meskipun proyek ini dirakit, perpustakaan tidak akan ditambahkan ke dalamnya dan Anda akan menjadi satu minggu lagi , seperti saya, duduk-duduk mencari masalah. Dan ... kamu tidak akan menemukannya. Oleh karena itu, bagi pemula dan orang-orang dengan mental yang lemah, bekerja dengan Buildozer dapat membawa ke klinik.
Jadi saya meludah ke traktor yang mati ini, pergi ke neraka, pergi ke github, mengunduh python-untuk-android, mengambil Crystax-NDK di situs off, menginstal Python 3.5 dan dengan tenang mengumpulkan APK proyek dengan cabang Python ketiga, yang ternyata jauh lebih sederhana, dibandingkan dengan Buildozer terkenal.
Bagaimana dengan layanan? Tapi tidak ada apa-apa. Mereka tidak bekerja. Lebih tepatnya, layanan yang dibuat dalam proyek Anda tidak akan dimulai dengan reboot smartphone, tidak peduli apa yang penulis artikel klaim tentang layanan di Kivy. Setelah ditemukan di Google Play dan menginstal proyeknya, saya menemukan bahwa tidak ada layanan dengan memulai kembali program dimulai. 100% layanan di Kivy dimulai hanya dengan peluncuran aplikasi itu sendiri. Nantinya, jika Anda menutup aplikasi, layanan akan terus bekerja dengan tenang sampai Anda mematikan perangkat.
Tentang Python 2 dan Python 3
Pada bulan Februari tahun ini, Moscow Python diadakan di kantor Yandex di Moskow, di mana Vladislav Shashkov membuat presentasi dengan tema: "Aplikasi seluler Python dengan kivy / buildozer adalah kunci kesuksesan". Jadi dia punya kebodohan untuk mengatakan bahwa Python 2 dalam perakitan APK lebih cepat dari Python 3. Jangan percaya siapa pun, ini tidak benar. Python 3 lebih cepat dari Python 2 pada prinsipnya! Ketika saya mengembangkan "Quotes of the Saints" (kemudian diasumsikan bahwa cabang Python kedua akan digunakan dalam perakitan), saya terkejut menemukan bahwa basis kutipan 20 MB, yang digunakan dalam aplikasi ketika tidak ada koneksi jaringan, dibaca menggunakan json. memuat sebanyak 13-16 detik di perangkat seluler! Dan basis yang sama, tetapi sudah dengan Python 3 diproses di perangkat dalam 1-2 detik! Buat kesimpulan sendiri ...
Tentang Bereaksi Asli
Ya, dalam artikel saya, saya memutuskan untuk menarik paralel antara Kivy dan kerangka kerja lain untuk pengembangan lintas platform. Di sini Anda hanya perlu membuka spoiler dan melihat bagaimana aplikasi yang sederhana, cepat dan elegan dibuat di React Native ...
ContohMari kita coba menggambar antarmuka penuh. Kami menulis ulang App.js menggunakan komponen dari pustaka asli-basis:
import React from 'react'; import {Container, Content} from 'native-base'; import {StyleSheet, Text, View} from 'react-native'; import AppFooter from './components/AppFooter.js'; const styles = StyleSheet.create({ container: { padding: 20 }, }); const App = () => ( <Container> <Content> <View style={styles.container}> <Text> Lorem ipsum... </Text> </View> </Content> <AppFooter/> </Container> ); export default App;
Kami melihat komponen AppFooter baru yang harus kami buat. Kami pergi ke folder ./components/ dan membuat file AppFooter.js dengan konten berikut:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; const AppFooter = () => ( <Footer> <FooterTab> <Button active> <Text></Text> </Button> <Button> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Semuanya siap untuk mencoba membangun aplikasi kita!
Tombol kami belum tahu cara beralih. Sudah waktunya untuk mengajar mereka. Untuk melakukan ini, Anda perlu melakukan dua hal: mempelajari cara menangani peristiwa klik dan mempelajari cara menyimpan keadaan. Mari kita mulai dengan negara. Karena kami menolak untuk menyimpan status dalam komponen, setelah memilih komponen murni dan toko global, kami akan menggunakan Redux.
Pertama-tama, kita harus menciptakan sisi kita.
import {createStore} from 'redux'; const initialState = {}; const store = createStore(reducers, initialState);
Mari kita buat kosong untuk reduksi. Di folder reduksi, buat file index.js dengan konten berikut:
export default (state = [], action) => { switch (action.type) { default: return state } };
Hubungkan reducers ke App.js:
import reducers from './reducers';
Sekarang kita perlu mendistribusikan penyimpanan kita pada komponen. Ini dilakukan dengan menggunakan komponen Penyedia khusus. Kami menghubungkannya ke proyek:
import {Provider} from 'react-redux';
Dan bungkus semua komponen dalam Penyedia. App.js yang diperbarui terlihat seperti ini:
import React from 'react'; import {Container, Content} from 'native-base'; import {StyleSheet, Text, View} from 'react-native'; import AppFooter from './components/AppFooter.js'; import {createStore} from 'redux'; import {Provider} from 'react-redux'; import reducers from './reducers'; const initialState = {}; const store = createStore(reducers, initialState); const styles = StyleSheet.create({ container: { padding: 20 }, }); const App = () => ( <Provider store={store}> <Container> <Content> <View style={styles.container}> <Text> Lorem ipsum... </Text> </View> </Content> <AppFooter/> </Container> </Provider> ); export default App;
Sekarang aplikasi kita dapat menyimpan kondisinya. Mari manfaatkan ini. Kami menambahkan status mode, secara default diatur ke ARTICLES. Ini berarti bahwa pada render pertama, aplikasi kita akan diatur untuk menampilkan daftar artikel.
const initialState = { mode: 'ARTICLES' };
Tidak buruk, tetapi menulis nilai string secara manual menyebabkan potensi kesalahan. Mari kita dapatkan konstanta. Buat file ./constants/index.js dengan konten berikut:
export const MODES = { ARTICLES: 'ARTICLES', PODCAST: 'PODCAST' };
Dan tulis ulang App.js:
import {MODES} from './constants'; const initialState = { mode: MODES.ARTICLES };
Nah, ada keadaan, saatnya untuk meneruskannya ke komponen footer. Mari kita lihat lagi ./components/AppFooter.js kami:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; const AppFooter = () => ( <Footer> <FooterTab> <Button active> <Text></Text> </Button> <Button> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Seperti yang dapat kita lihat, keadaan sakelar ditentukan menggunakan properti aktif dari komponen Button. Mari kita dorong keadaan aplikasi saat ini ke Button. Ini tidak sulit, komponen utama kap adalah komponen Penyedia yang kami terhubung sebelumnya. Tetap hanya untuk mengambil status saat ini darinya dan memasukkan komponen AppFooter ke properti (alat peraga). Pertama-tama, kami memodifikasi AppFooter kami sehingga keadaan tombol dapat dikontrol dengan melewati mode melalui alat peraga:
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; import {MODES} from "../constants"; const AppFooter = ({mode = MODES.ARTICLES}) => ( <Footer> <FooterTab> <Button active={mode === MODES.ARTICLES}> <Text></Text> </Button> <Button active={mode === MODES.PODCAST}> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Sekarang mari kita mulai membuat wadah. Buat file ./containers/AppFooterContainer.js.
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {MODES} from "../constants"; const AppFooterContainer = () => ( <AppFooter mode={MODES.ARTICLES} /> ); export default AppFooterContainer;
Dan hubungkan wadah AppFooterContainer di App.js bukannya komponen AppFooter. Sejauh ini, wadah kami tidak berbeda dari komponen, tetapi semuanya akan berubah segera setelah kami menghubungkannya ke keadaan aplikasi. Ayo lakukan!
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {connect} from 'react-redux'; const mapStateToProps = (state) => ({ mode: state.mode }); const AppFooterContainer = ({mode}) => ( <AppFooter mode={mode} /> ); export default connect( mapStateToProps )(AppFooterContainer);
Sangat fungsional! Semua fitur menjadi bersih. Apa yang sedang terjadi di sini? Kami menghubungkan wadah kami ke negara menggunakan fungsi terhubung dan menghubungkan alat peraga ke konten negara global menggunakan fungsi mapStateToProps. Sangat bersih dan asri.
Jadi, kami telah belajar mendistribusikan data dari atas ke bawah. Sekarang kita perlu belajar bagaimana mengubah keadaan global kita dari bawah ke atas. Tindakan dirancang untuk menghasilkan peristiwa tentang perlunya mengubah negara global. Mari kita membuat tindakan yang terjadi ketika tombol diklik.
Buat file ./actions/index.js:
import { SET_MODE } from './actionTypes'; export const setMode = (mode) => ({type: SET_MODE, mode});
Dan file ./actions/actionTypes, di mana kita akan menyimpan konstanta dengan nama tindakan:
export const SET_MODE = 'SET_MODE';
Tindakan membuat objek dengan nama acara dan kumpulan data yang menyertai acara ini, dan tidak lebih. Sekarang kita akan belajar bagaimana membuat acara ini. Kami kembali ke wadah AppFooterContainer dan menghubungkan fungsi mapDispatchToProps yang akan menghubungkan event dispatcher ke alat peraga wadah.
import React from 'react'; import AppFooter from '../components/AppFooter.js'; import {connect} from 'react-redux'; import {setMode} from '../actions'; const mapStateToProps = (state) => ({ mode: state.mode }); const mapDispatchToProps = (dispatch) => ({ setMode(mode) { dispatch(setMode(mode)); } }); const AppFooterContainer = ({mode, setMode}) => ( <AppFooter mode={mode} setMode={setMode} /> ); export default connect( mapStateToProps, mapDispatchToProps )(AppFooterContainer);
Ya, kami memiliki fungsi yang memunculkan acara SET_MODE dan kami melewatkannya ke komponen AppFooter. Masih ada dua masalah:
Tidak ada yang memanggil fungsi ini.
Tidak ada yang mendengarkan acara tersebut.
Kami akan menangani masalah pertama. Kami pergi ke komponen AppFooter dan menghubungkan panggilan ke fungsi setMode.
import React from 'react'; import {Footer, FooterTab, Button, Text} from 'native-base'; import {MODES} from "../constants"; const AppFooter = ({mode = MODES.ARTICLES, setMode = () => {}}) => ( <Footer> <FooterTab> <Button active={mode === MODES.ARTICLES} onPress={ () => setMode(MODES.ARTICLES)}> <Text></Text> </Button> <Button active={mode === MODES.PODCAST} onPress={ () => setMode(MODES.PODCAST)}> <Text></Text> </Button> </FooterTab> </Footer> ); export default AppFooter;
Sekarang, ketika tombol ditekan, acara SET_MODE akan dinaikkan. Masih belajar bagaimana mengubah negara global saat muncul. Kami pergi ke ./reducers/index.js yang sebelumnya dibuat dan membuat peredam untuk acara ini:
import { SET_MODE } from '../actions/actionTypes'; export default (state = [], action) => { switch (action.type) { case SET_MODE: { return Object.assign({}, state, { mode: action.mode }); } default: return state } };
Hebat! Sekarang mengklik tombol menghasilkan suatu peristiwa yang mengubah keadaan global, dan catatan kaki, setelah menerima perubahan ini, menggambar ulang tombol.
Artikel asliBenar, sangat sederhana? Menakutkan membayangkan berapa banyak programmer yang mati karena usia tua pada proyek React Native dan berapa banyak uang yang dibayarkan untuk semua aib ini. Hasil dari semua ini adalah contoh kecil, sedikit lebih rumit daripada Hello World.
Sekali setelah program konser album "... Dan Keadilan untuk Semua" pada tahun 1988, pemimpin Metallica James Hetfield berkata, "Ini sangat ... tetapi tidak mungkin untuk bermain hidup-hidup." Jadi, setelah saya menulis kode sampel pada React Native, saya menjadi solidaritas dengan James - itu adalah ... tetapi tidak mungkin untuk menulis hidup-hidup!
Dan di sini adalah bagaimana hal yang sama dilakukan menggunakan kerangka kerja Kivy:
from kivy.app import App from kivy.factory import Factory from kivy.lang import Builder Builder.load_string(""" <MyButton@Button>: background_down: 'button_down.png' background_normal: 'button_normal.png' color: 0, 0, 0, 1 bold: True on_press: self.parent.parent.ids.textEdit.text = self.text; \ self.color = [.10980392156862745, .5372549019607843, .996078431372549, 1] on_release: self.color = [0, 0, 0, 1] <MyActivity@BoxLayout>: orientation: 'vertical' TextInput: id: textEdit BoxLayout: size_hint_y: None height: dp(45) MyButton: text: '' MyButton: text: '' """) class Program(App): def build(self): my_activity = Factory.MyActivity() return my_activity Program().run()
Sangat sederhana sehingga komentar pun berlebihan di sini.
Ya, Anda mungkin tidak tahu tentang ini, tetapi semuanya ditulis dalam Kivy:
vimeo.com/29348760vimeo.com/206290310vimeo.com/25680681www.youtube.com/watch?v=u4NRu7mBXtAwww.youtube.com/watch?v=9rk9OQLSoJwwww.youtube.com/watch?v=aa9LXpg_gd0www.youtube.com/watch?v=FhRXAD8-UkEwww.youtube.com/watch?v=GJ3f88ebDqc&t=111swww.youtube.com/watch?v=D_M1I9GvpYswww.youtube.com/watch?v=VotPQafL7NwKesimpulannya, saya memberikan video aplikasi:
Tulis di komentar artikel apa yang ingin Anda lihat tentang Kivy di halaman Habr. Jika memungkinkan, semua keinginan akan terwujud. Sampai ketemu lagi, dzzya!