Archie Mistry

Archie Mistry

1575739443

How to Read the RxJS 6 Sources: Map and Pipe

Disclaimer: This series is just my notes as I read through the RxJS sources. I’ll provide a summary of the main points at the end of the article, so don’t feel too bogged down with the details

Welcome back. Today I’m very excited, because I’m finally going to dig into how pipe is implemented in RxJS. This article will start with an overview of how map and pipe work, and then will delve into the RxJS sources.

Previously

In the last article, I looked into the of method for creating an observable. I’ll continue working off of that simple Stackblitz example, except this time, I’ll uncomment map and pipe. You don’t have to be familiar with the previous article to follow this one. Here’s the excerpt from Stackblitz:

This is image title
map attack!

Here’s a link to the Stackblitz.

Before I dive into the sources, let’s talk about map and pipe. Before trying to read any source, it’s best to have a high-level understanding of how everything works. Otherwise, it’s too easy to get lost in the details.

I know these two things before going in:

  • map is an operator that transforms data by applying a function
  • pipe composes operators (like map, filter, etc)

Map

Map’s job is to transform things

map is a pretty simple operator. It takes a projection function, and applies it to each value that comes from the source observable.

In this example, the observable returned by of('World’) is the source observable, and the single value 'World' is going to be pipe’d through to map’s projection function, which looks like this:

x => `Hello ${x}!` // projection function
// It's used like this:
of('World').pipe(map(x => `Hello ${x}!`));

The projection function will receive 'World' as its input parameter x, and will create the string Hello World!.
map wraps the project function in an observable, which then emits the string value Hello World!. Remember, operators always return observables.

map wraps the projection function in an observable, and starts emitting string values.

I’ve written about the basics of map and other operators pretty extensively in this article. I’ll cover some of that material again here.

Basically, if you understand how Array.prototype.map works, most of that knowledge will carry over to observables.

We’ll see more on map later in this article. Let’s look at pipe next.

Pipe

pipe is the star of this article. Unlike map, which is an operator, pipe is a method on Observable which is used for composing operators. pipe was introduced to RxJS in v5.5 to take code that looked like this:

of(1,2,3).map(x => x + 1).filter(x => x > 2);

and turn it into this

of(1,2,3).pipe(
  map(x => x + 1),
  filter(x => x > 2)
);

Same output, same concept (composing operators), different syntax.
pipe offers the following benefits:

  • It cleans up Observable.prototype by removing operators
  • It makes the RxJS library more tree-shakeable
  • It makes it easier to write and use third-party operators (since you don’t have to worry about patching Observable.prototype).

Nicholas Jamieson provides a great explanation of the benefits of using pipe for composition in this article.

Quick detour (skip this section if you are comfortable with pipe)

If you’re unfamiliar with using pipe for composition, it’s worthwhile to see how it works on regular functions before seeing how it works with operators. Let’s look at a simplified version of pipe which acts on normal functions:

const pipe = (...fns) => 
           initialVal => 
           fns.reduce((g,f) => f(g), initialVal);

In this example, pipe is a function which accepts functions as arguments. Those arguments are collected into an array called fns through use of ES6 rest parameters (…fns). pipe then returns a function which accepts an initialValue to be passed into reduce in the following step. This is the value which is passed into the first function in fns, the output of which is then fed into the second function in fns, which is then fed into the third…and so on. Hence, a pipeline.
For example:

pipe.ts

const pipe = (...fns) => initialVal => fns.reduce((g,f) => f(g), initialVal);
const add1 = x => x + 1;
const mul2 = x => x * 2;

const res = pipe(add1,mul2)(0); // mul2(add1(0)) === 2

You can experiment with a simple pipe at this stackblitz link.

In RxJS, the idea is that you create a pipeline of operators (such as map and filter) that you want to apply to each value emitted by a source observable, of(1,2,3) in this example.

This approach lets you create small, reusable operators like map and filter, and compose them together when needed using pipe.

Composition is a pretty fascinating topic, although I can hardly do it justice.
I recommend Eric Elliott’s series on the topic if you want to learn more.

Enough talk! Get to the Sources!

I’ll start by adding a debugger statement into map. This will give me access to map within the dev tools debugger, as well as a way to step up into pipe.

This is image title

and, in the dev tools:

This is image title

Now that I’m oriented in the call stack, and I can start to dig around.

Notice that in the call stack, it’s Observable.subscribe that’s kicking everything off. Because observables tend to be lazy, no data will flow through the pipe and map until we subscribe to the observable.

var sub = source.subscribe(...)

Looking inside of map, I notice that MapOperator and MapSubscriber look interesting:

This is image title

On line 55, source is the observable produced by of('World'). It is subscribed to on line 56, causing it to emit its one value, 'World', and then complete.

On line 56, an instance of MapSubscriber is created, and passed into source.subscribe. We’ll see later that the projection function is invoked inside of MapSubscriber’s _next method.

On line 56, this.project is the projection function passed into map:

This is image title

and this.thisArg can be ignored for now. So line 56 is doing the following:

return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
  1. calling subscribe on source, which is the observable returned by of('World').
  2. The observer ( next, error, complete, etc) which is passed into source.subscribe is going to be the Subscriber returned by MapSubscriber, which takes the current subscriber, and the project function passed into map as its arguments.

As a quick aside, this is a very common pattern for operators in RxJS. In fact, they all seem to follow the following template:

  • export a public function, like map or filter or expand.
  • export a class which implements Operator, such as MapOperator. This class implements Operator call method. It subscribes to the source observable, like
    return source.subscribe(new MapSubscriber(…));
    This links the observables into a subscriber/observer pipeline.
  • A class which extends Subscriber. This class will implement methods such as _next.
    This is where the logic that makes each operator unique lives. For example, in map, the projection function will be invoked inside of MapSubscriber’s _next method. In filter the predicate function will be invoked inside of FilterSubscriber’s _next method, and so on.

I’ll provide an example of how to write your own operator in a future article (although it’s usually easier to just pipe together existing operators). In the meantime, the RxJS sources provide a nice guide here, and Nicholas Jamieson has a great example in this article.

Anyways, back to the debugging session.

Eventually, once subscribe is called, MapSubscriber._next will be invoked.

This is image title

Notice that the projection function, project, which was passed into map is invoked on line 81, and the results (in this case 'Hello World!' ) will be returned, and then passed into this.destination.next(result) on line 86.

This is image title
stepping into this.project.call puts us in the lambda we passed into the call to map

This explains how map applies the projection function to each value emitted by the source observable when it is subscribed to. That’s really all there to this step. If there were another operator in the pipeline, the observable returned by map would be fed into it.

This is a good example of how data flows through a single operator. But how does it flow through multiple operators…

Pipe (again)

To answer that, I must dig into pipe. It’s being invoked on the observable which is returned from of('World').

This is image title

pipeFromArray is called on line 331 with operations, which is an array of all operators passed into pipe. In this case, it’s just the lonely map operator:

This is image title

operations could hold many, many operators

The function returned from the call to pipeFromArray(operations) is invoked with this, which is a reference to the observable returned from of('World').

This is image title

Since there is only one operator in this case (map), line 29 returns it.

Line 33 is interesting. It’s where all of the operators passed into pipe are composed using Array.prototype.reduce. It’s not invoked in situations where it is passed only one operator (perhaps for performance reasons?).

Let’s look at a slightly more complex example, with multiple map operators.

Multiple maps

Now that I have an understanding of what map and pipe are doing, I’ll try a more complicated example. This time, I’ll use the map operator three times!

This is image title

Hello World of RxJS

The only real difference is that pipe will use reduce this time:

This is image title

The input variable is still the observable returned from of('World').

This is image title

By stepping through each function in fns as it is called by reduce, I can see the string being built up as it passes through each one of the map operators. Eventually producing the string Hello World of RxJS

This is image title

With an understanding of how data flows through a single operator, it’s not hard to extend that understanding to multiple operators.

A little map and a little filter

Just for fun, I want to throw filter in the mix. The goal here is to confirm that map isn’t unique. I want to see that all operators follow that similar pattern.

This is image title
Will log values 3 and 4

In this example, of(1,2,3) will return an observable which, upon subscription, will emit three separate values, 1, 2, and 3, and will then complete. Each of these three values will be fed into the pipeline one at a time. map will add one to each, and then re-emit the new values one-by-one on the observable it returns. filter subscribes to the observable returned by map, and runs each value through its predicate function ( x => x > 2 ). It will return an observable which emits any value which is greater than 2. In this case, it will emit values 3 and 4.

If you want to see a more detailed explanation of the subscriber chain and how operators subscribe to one another,

Summary

  • We’ve seen that operators like map and filter are functions which take in and return observables.
  • Each operator exposes a public function like map or filter, which is what we import from 'rxjs/operators' and pass into pipe.
  • Each operator has a *Operator class which implements the Operator interface, so that it can subscribe to other observables.
  • Each operator has a *Subscriber class which contains the logic for that operator (invocation of the projection function for map, invocation of the predicate function for filter, etc).
  • We’ve also seen how pipe is used to compose operators together. Internally, it’s taking the values emitted by the source observable, and reducing it over the list of operators.

In the next article, I’ll look at some more advanced maps, and see how higher order observables are implemented. 🗺

#javascript #RxJS #Map #Pipe

What is GEEK

Buddha Community

How to Read the RxJS 6 Sources: Map and Pipe
Archie Mistry

Archie Mistry

1575739443

How to Read the RxJS 6 Sources: Map and Pipe

Disclaimer: This series is just my notes as I read through the RxJS sources. I’ll provide a summary of the main points at the end of the article, so don’t feel too bogged down with the details

Welcome back. Today I’m very excited, because I’m finally going to dig into how pipe is implemented in RxJS. This article will start with an overview of how map and pipe work, and then will delve into the RxJS sources.

Previously

In the last article, I looked into the of method for creating an observable. I’ll continue working off of that simple Stackblitz example, except this time, I’ll uncomment map and pipe. You don’t have to be familiar with the previous article to follow this one. Here’s the excerpt from Stackblitz:

This is image title
map attack!

Here’s a link to the Stackblitz.

Before I dive into the sources, let’s talk about map and pipe. Before trying to read any source, it’s best to have a high-level understanding of how everything works. Otherwise, it’s too easy to get lost in the details.

I know these two things before going in:

  • map is an operator that transforms data by applying a function
  • pipe composes operators (like map, filter, etc)

Map

Map’s job is to transform things

map is a pretty simple operator. It takes a projection function, and applies it to each value that comes from the source observable.

In this example, the observable returned by of('World’) is the source observable, and the single value 'World' is going to be pipe’d through to map’s projection function, which looks like this:

x => `Hello ${x}!` // projection function
// It's used like this:
of('World').pipe(map(x => `Hello ${x}!`));

The projection function will receive 'World' as its input parameter x, and will create the string Hello World!.
map wraps the project function in an observable, which then emits the string value Hello World!. Remember, operators always return observables.

map wraps the projection function in an observable, and starts emitting string values.

I’ve written about the basics of map and other operators pretty extensively in this article. I’ll cover some of that material again here.

Basically, if you understand how Array.prototype.map works, most of that knowledge will carry over to observables.

We’ll see more on map later in this article. Let’s look at pipe next.

Pipe

pipe is the star of this article. Unlike map, which is an operator, pipe is a method on Observable which is used for composing operators. pipe was introduced to RxJS in v5.5 to take code that looked like this:

of(1,2,3).map(x => x + 1).filter(x => x > 2);

and turn it into this

of(1,2,3).pipe(
  map(x => x + 1),
  filter(x => x > 2)
);

Same output, same concept (composing operators), different syntax.
pipe offers the following benefits:

  • It cleans up Observable.prototype by removing operators
  • It makes the RxJS library more tree-shakeable
  • It makes it easier to write and use third-party operators (since you don’t have to worry about patching Observable.prototype).

Nicholas Jamieson provides a great explanation of the benefits of using pipe for composition in this article.

Quick detour (skip this section if you are comfortable with pipe)

If you’re unfamiliar with using pipe for composition, it’s worthwhile to see how it works on regular functions before seeing how it works with operators. Let’s look at a simplified version of pipe which acts on normal functions:

const pipe = (...fns) => 
           initialVal => 
           fns.reduce((g,f) => f(g), initialVal);

In this example, pipe is a function which accepts functions as arguments. Those arguments are collected into an array called fns through use of ES6 rest parameters (…fns). pipe then returns a function which accepts an initialValue to be passed into reduce in the following step. This is the value which is passed into the first function in fns, the output of which is then fed into the second function in fns, which is then fed into the third…and so on. Hence, a pipeline.
For example:

pipe.ts

const pipe = (...fns) => initialVal => fns.reduce((g,f) => f(g), initialVal);
const add1 = x => x + 1;
const mul2 = x => x * 2;

const res = pipe(add1,mul2)(0); // mul2(add1(0)) === 2

You can experiment with a simple pipe at this stackblitz link.

In RxJS, the idea is that you create a pipeline of operators (such as map and filter) that you want to apply to each value emitted by a source observable, of(1,2,3) in this example.

This approach lets you create small, reusable operators like map and filter, and compose them together when needed using pipe.

Composition is a pretty fascinating topic, although I can hardly do it justice.
I recommend Eric Elliott’s series on the topic if you want to learn more.

Enough talk! Get to the Sources!

I’ll start by adding a debugger statement into map. This will give me access to map within the dev tools debugger, as well as a way to step up into pipe.

This is image title

and, in the dev tools:

This is image title

Now that I’m oriented in the call stack, and I can start to dig around.

Notice that in the call stack, it’s Observable.subscribe that’s kicking everything off. Because observables tend to be lazy, no data will flow through the pipe and map until we subscribe to the observable.

var sub = source.subscribe(...)

Looking inside of map, I notice that MapOperator and MapSubscriber look interesting:

This is image title

On line 55, source is the observable produced by of('World'). It is subscribed to on line 56, causing it to emit its one value, 'World', and then complete.

On line 56, an instance of MapSubscriber is created, and passed into source.subscribe. We’ll see later that the projection function is invoked inside of MapSubscriber’s _next method.

On line 56, this.project is the projection function passed into map:

This is image title

and this.thisArg can be ignored for now. So line 56 is doing the following:

return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg));
  1. calling subscribe on source, which is the observable returned by of('World').
  2. The observer ( next, error, complete, etc) which is passed into source.subscribe is going to be the Subscriber returned by MapSubscriber, which takes the current subscriber, and the project function passed into map as its arguments.

As a quick aside, this is a very common pattern for operators in RxJS. In fact, they all seem to follow the following template:

  • export a public function, like map or filter or expand.
  • export a class which implements Operator, such as MapOperator. This class implements Operator call method. It subscribes to the source observable, like
    return source.subscribe(new MapSubscriber(…));
    This links the observables into a subscriber/observer pipeline.
  • A class which extends Subscriber. This class will implement methods such as _next.
    This is where the logic that makes each operator unique lives. For example, in map, the projection function will be invoked inside of MapSubscriber’s _next method. In filter the predicate function will be invoked inside of FilterSubscriber’s _next method, and so on.

I’ll provide an example of how to write your own operator in a future article (although it’s usually easier to just pipe together existing operators). In the meantime, the RxJS sources provide a nice guide here, and Nicholas Jamieson has a great example in this article.

Anyways, back to the debugging session.

Eventually, once subscribe is called, MapSubscriber._next will be invoked.

This is image title

Notice that the projection function, project, which was passed into map is invoked on line 81, and the results (in this case 'Hello World!' ) will be returned, and then passed into this.destination.next(result) on line 86.

This is image title
stepping into this.project.call puts us in the lambda we passed into the call to map

This explains how map applies the projection function to each value emitted by the source observable when it is subscribed to. That’s really all there to this step. If there were another operator in the pipeline, the observable returned by map would be fed into it.

This is a good example of how data flows through a single operator. But how does it flow through multiple operators…

Pipe (again)

To answer that, I must dig into pipe. It’s being invoked on the observable which is returned from of('World').

This is image title

pipeFromArray is called on line 331 with operations, which is an array of all operators passed into pipe. In this case, it’s just the lonely map operator:

This is image title

operations could hold many, many operators

The function returned from the call to pipeFromArray(operations) is invoked with this, which is a reference to the observable returned from of('World').

This is image title

Since there is only one operator in this case (map), line 29 returns it.

Line 33 is interesting. It’s where all of the operators passed into pipe are composed using Array.prototype.reduce. It’s not invoked in situations where it is passed only one operator (perhaps for performance reasons?).

Let’s look at a slightly more complex example, with multiple map operators.

Multiple maps

Now that I have an understanding of what map and pipe are doing, I’ll try a more complicated example. This time, I’ll use the map operator three times!

This is image title

Hello World of RxJS

The only real difference is that pipe will use reduce this time:

This is image title

The input variable is still the observable returned from of('World').

This is image title

By stepping through each function in fns as it is called by reduce, I can see the string being built up as it passes through each one of the map operators. Eventually producing the string Hello World of RxJS

This is image title

With an understanding of how data flows through a single operator, it’s not hard to extend that understanding to multiple operators.

A little map and a little filter

Just for fun, I want to throw filter in the mix. The goal here is to confirm that map isn’t unique. I want to see that all operators follow that similar pattern.

This is image title
Will log values 3 and 4

In this example, of(1,2,3) will return an observable which, upon subscription, will emit three separate values, 1, 2, and 3, and will then complete. Each of these three values will be fed into the pipeline one at a time. map will add one to each, and then re-emit the new values one-by-one on the observable it returns. filter subscribes to the observable returned by map, and runs each value through its predicate function ( x => x > 2 ). It will return an observable which emits any value which is greater than 2. In this case, it will emit values 3 and 4.

If you want to see a more detailed explanation of the subscriber chain and how operators subscribe to one another,

Summary

  • We’ve seen that operators like map and filter are functions which take in and return observables.
  • Each operator exposes a public function like map or filter, which is what we import from 'rxjs/operators' and pass into pipe.
  • Each operator has a *Operator class which implements the Operator interface, so that it can subscribe to other observables.
  • Each operator has a *Subscriber class which contains the logic for that operator (invocation of the projection function for map, invocation of the predicate function for filter, etc).
  • We’ve also seen how pipe is used to compose operators together. Internally, it’s taking the values emitted by the source observable, and reducing it over the list of operators.

In the next article, I’ll look at some more advanced maps, and see how higher order observables are implemented. 🗺

#javascript #RxJS #Map #Pipe

渚  直樹

渚 直樹

1635917640

ループを使用して、Rustのデータを反復処理します

このモジュールでは、Rustでハッシュマップ複合データ型を操作する方法について説明します。ハッシュマップのようなコレクション内のデータを反復処理するループ式を実装する方法を学びます。演習として、要求された注文をループし、条件をテストし、さまざまなタイプのデータを処理することによって車を作成するRustプログラムを作成します。

さび遊び場

錆遊び場は錆コンパイラにブラウザインタフェースです。言語をローカルにインストールする前、またはコンパイラが利用できない場合は、Playgroundを使用してRustコードの記述を試すことができます。このコース全体を通して、サンプルコードと演習へのPlaygroundリンクを提供します。現時点でRustツールチェーンを使用できない場合でも、コードを操作できます。

Rust Playgroundで実行されるすべてのコードは、ローカルの開発環境でコンパイルして実行することもできます。コンピューターからRustコンパイラーと対話することを躊躇しないでください。Rust Playgroundの詳細については、What isRust?をご覧ください。モジュール。

学習目標

このモジュールでは、次のことを行います。

  • Rustのハッシュマップデータ型、およびキーと値にアクセスする方法を確認してください
  • ループ式を使用してRustプログラムのデータを反復処理する方法を探る
  • Rustプログラムを作成、コンパイル、実行して、ループを使用してハッシュマップデータを反復処理します

Rustのもう1つの一般的なコレクションの種類は、ハッシュマップです。このHashMap<K, V>型は、各キーKをその値にマッピングすることによってデータを格納しますV。ベクトル内のデータは整数インデックスを使用してアクセスされますが、ハッシュマップ内のデータはキーを使用してアクセスされます。

ハッシュマップタイプは、オブジェクト、ハッシュテーブル、辞書などのデータ項目の多くのプログラミング言語で使用されます。

ベクトルのように、ハッシュマップは拡張可能です。データはヒープに格納され、ハッシュマップアイテムへのアクセスは実行時にチェックされます。

ハッシュマップを定義する

次の例では、書評を追跡するためのハッシュマップを定義しています。ハッシュマップキーは本の名前であり、値は読者のレビューです。

use std::collections::HashMap;
let mut reviews: HashMap<String, String> = HashMap::new();

reviews.insert(String::from("Ancient Roman History"), String::from("Very accurate."));
reviews.insert(String::from("Cooking with Rhubarb"), String::from("Sweet recipes."));
reviews.insert(String::from("Programming in Rust"), String::from("Great examples."));

このコードをさらに詳しく調べてみましょう。最初の行に、新しいタイプの構文が表示されます。

use std::collections::HashMap;

このuseコマンドは、Rust標準ライブラリの一部HashMapからの定義をcollectionsプログラムのスコープに取り込みます。この構文は、他のプログラミング言語がインポートと呼ぶものと似ています。

HashMap::newメソッドを使用して空のハッシュマップを作成します。reviews必要に応じてキーと値を追加または削除できるように、変数を可変として宣言します。この例では、ハッシュマップのキーと値の両方がStringタイプを使用しています。

let mut reviews: HashMap<String, String> = HashMap::new();

キーと値のペアを追加します

このinsert(<key>, <value>)メソッドを使用して、ハッシュマップに要素を追加します。コードでは、構文は<hash_map_name>.insert()次のとおりです。

reviews.insert(String::from("Ancient Roman History"), String::from("Very accurate."));

キー値を取得する

ハッシュマップにデータを追加した後、get(<key>)メソッドを使用してキーの特定の値を取得できます。

// Look for a specific review
let book: &str = "Programming in Rust";
println!("\nReview for \'{}\': {:?}", book, reviews.get(book));

出力は次のとおりです。

Review for 'Programming in Rust': Some("Great examples.")

ノート

出力には、書評が単なる「すばらしい例」ではなく「Some( "すばらしい例。")」として表示されていることに注意してください。getメソッドはOption<&Value>型を返すため、Rustはメソッド呼び出しの結果を「Some()」表記でラップします。

キーと値のペアを削除します

この.remove()メソッドを使用して、ハッシュマップからエントリを削除できます。get無効なハッシュマップキーに対してメソッドを使用すると、getメソッドは「なし」を返します。

// Remove book review
let obsolete: &str = "Ancient Roman History";
println!("\n'{}\' removed.", obsolete);
reviews.remove(obsolete);

// Confirm book review removed
println!("\nReview for \'{}\': {:?}", obsolete, reviews.get(obsolete));

出力は次のとおりです。

'Ancient Roman History' removed.
Review for 'Ancient Roman History': None

このコードを試して、このRustPlaygroundでハッシュマップを操作できます。

演習:ハッシュマップを使用して注文を追跡する
この演習では、ハッシュマップを使用するように自動車工場のプログラムを変更します。

ハッシュマップキーと値のペアを使用して、車の注文に関する詳細を追跡し、出力を表示します。繰り返しになりますが、あなたの課題は、サンプルコードを完成させてコンパイルして実行することです。

この演習のサンプルコードで作業するには、次の2つのオプションがあります。

  • コードをコピーして、ローカル開発環境で編集します。
  • 準備されたRustPlaygroundでコードを開きます。

ノート

サンプルコードで、todo!マクロを探します。このマクロは、完了するか更新する必要があるコードを示します。

現在のプログラムをロードする

最初のステップは、既存のプログラムコードを取得することです。

  1. 編集のために既存のプログラムコードを開きます。コードは、データ型宣言、および定義のため含みcar_qualitycar_factoryおよびmain機能を。

次のコードをコピーしてローカル開発環境で編集する
か、この準備されたRustPlaygroundでコードを開きます。

#[derive(PartialEq, Debug)]
struct Car { color: String, motor: Transmission, roof: bool, age: (Age, u32) }

#[derive(PartialEq, Debug)]
enum Transmission { Manual, SemiAuto, Automatic }

#[derive(PartialEq, Debug)]
enum Age { New, Used }

// Get the car quality by testing the value of the input argument
// - miles (u32)
// Return tuple with car age ("New" or "Used") and mileage
fn car_quality (miles: u32) -> (Age, u32) {

    // Check if car has accumulated miles
    // Return tuple early for Used car
    if miles > 0 {
        return (Age::Used, miles);
    }

    // Return tuple for New car, no need for "return" keyword or semicolon
    (Age::New, miles)
}

// Build "Car" using input arguments
fn car_factory(order: i32, miles: u32) -> Car {
    let colors = ["Blue", "Green", "Red", "Silver"];

    // Prevent panic: Check color index for colors array, reset as needed
    // Valid color = 1, 2, 3, or 4
    // If color > 4, reduce color to valid index
    let mut color = order as usize;
    if color > 4 {        
        // color = 5 --> index 1, 6 --> 2, 7 --> 3, 8 --> 4
        color = color - 4;
    }

    // Add variety to orders for motor type and roof type
    let mut motor = Transmission::Manual;
    let mut roof = true;
    if order % 3 == 0 {          // 3, 6, 9
        motor = Transmission::Automatic;
    } else if order % 2 == 0 {   // 2, 4, 8, 10
        motor = Transmission::SemiAuto;
        roof = false;
    }                            // 1, 5, 7, 11

    // Return requested "Car"
    Car {
        color: String::from(colors[(color-1) as usize]),
        motor: motor,
        roof: roof,
        age: car_quality(miles)
    }
}

fn main() {
    // Initialize counter variable
    let mut order = 1;
    // Declare a car as mutable "Car" struct
    let mut car: Car;

    // Order 6 cars, increment "order" for each request
    // Car order #1: Used, Hard top
    car = car_factory(order, 1000);
    println!("{}: {:?}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

    // Car order #2: Used, Convertible
    order = order + 1;
    car = car_factory(order, 2000);
    println!("{}: {:?}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);    

    // Car order #3: New, Hard top
    order = order + 1;
    car = car_factory(order, 0);
    println!("{}: {:?}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

    // Car order #4: New, Convertible
    order = order + 1;
    car = car_factory(order, 0);
    println!("{}: {:?}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

    // Car order #5: Used, Hard top
    order = order + 1;
    car = car_factory(order, 3000);
    println!("{}: {:?}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

    // Car order #6: Used, Hard top
    order = order + 1;
    car = car_factory(order, 4000);
    println!("{}: {:?}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);
}

2. プログラムをビルドします。次のセクションに進む前に、コードがコンパイルされて実行されることを確認してください。

次の出力が表示されます。

1: Used, Hard top = true, Manual, Blue, 1000 miles
2: Used, Hard top = false, SemiAuto, Green, 2000 miles
3: New, Hard top = true, Automatic, Red, 0 miles
4: New, Hard top = false, SemiAuto, Silver, 0 miles
5: Used, Hard top = true, Manual, Blue, 3000 miles
6: Used, Hard top = true, Automatic, Green, 4000 miles

注文の詳細を追跡するためのハッシュマップを追加する

現在のプログラムは、各車の注文を処理し、各注文が完了した後に要約を印刷します。car_factory関数を呼び出すたびにCar、注文の詳細を含む構造体が返され、注文が実行されます。結果はcar変数に格納されます。

お気づきかもしれませんが、このプログラムにはいくつかの重要な機能がありません。すべての注文を追跡しているわけではありません。car変数は、現在の注文の詳細のみを保持しています。関数carの結果で変数が更新されるたびcar_factoryに、前の順序の詳細が上書きされます。

ファイリングシステムのようにすべての注文を追跡するために、プログラムを更新する必要があります。この目的のために、<K、V>ペアでハッシュマップを定義します。ハッシュマップキーは、車の注文番号に対応します。ハッシュマップ値は、Car構造体で定義されているそれぞれの注文の詳細になります。

  1. ハッシュマップを定義するには、main関数の先頭、最初の中括弧の直後に次のコードを追加します{
// Initialize a hash map for the car orders
    // - Key: Car order number, i32
    // - Value: Car order details, Car struct
    use std::collections::HashMap;
    let mut orders: HashMap<i32, Car> = HashMap;

2. ordersハッシュマップを作成するステートメントの構文の問題を修正します。

ヒント

ハッシュマップを最初から作成しているので、おそらくこのnew()メソッドを使用することをお勧めします。

3. プログラムをビルドします。次のセクションに進む前に、コードがコンパイルされていることを確認してください。コンパイラからの警告メッセージは無視してかまいません。

ハッシュマップに値を追加する

次のステップは、履行された各自動車注文をハッシュマップに追加することです。

このmain関数では、car_factory車の注文ごとに関数を呼び出します。注文が履行された後、println!マクロを呼び出して、car変数に格納されている注文の詳細を表示します。

// Car order #1: Used, Hard top
    car = car_factory(order, 1000);
    println!("{}: {}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

    ...

    // Car order #6: Used, Hard top
    order = order + 1;
    car = car_factory(order, 4000);
    println!("{}: {}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

新しいハッシュマップで機能するように、これらのコードステートメントを修正します。

  • car_factory関数の呼び出しは保持します。返された各Car構造体は、ハッシュマップの<K、V>ペアの一部として格納されます。
  • println!マクロの呼び出しを更新して、ハッシュマップに保存されている注文の詳細を表示します。
  1. main関数で、関数の呼び出しcar_factoryとそれに伴うprintln!マクロの呼び出しを見つけます。
// Car order #1: Used, Hard top
    car = car_factory(order, 1000);
    println!("{}: {}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

    ...

    // Car order #6: Used, Hard top
    order = order + 1;
    car = car_factory(order, 4000);
    println!("{}: {}, Hard top = {}, {:?}, {}, {} miles", order, car.age.0, car.roof, car.motor, car.color, car.age.1);

2. すべての自動車注文のステートメントの完全なセットを次の改訂されたコードに置き換えます。

// Car order #1: Used, Hard top
    car = car_factory(order, 1000);
    orders(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

    // Car order #2: Used, Convertible
    order = order + 1;
    car = car_factory(order, 2000);
    orders(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

    // Car order #3: New, Hard top
    order = order + 1;
    car = car_factory(order, 0);
    orders(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

    // Car order #4: New, Convertible
    order = order + 1;
    car = car_factory(order, 0);
    orders(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

    // Car order #5: Used, Hard top
    order = order + 1;
    car = car_factory(order, 3000);
    orders(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

    // Car order #6: Used, Hard top
    order = order + 1;
    car = car_factory(order, 4000);
    orders(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

3. 今すぐプログラムをビルドしようとすると、コンパイルエラーが表示されます。<K、V>ペアをordersハッシュマップに追加するステートメントに構文上の問題があります。問題がありますか?先に進んで、ハッシュマップに順序を追加する各ステートメントの問題を修正してください。

ヒント

ordersハッシュマップに直接値を割り当てることはできません。挿入を行うにはメソッドを使用する必要があります。

プログラムを実行する

プログラムが正常にビルドされると、次の出力が表示されます。

Car order 1: Some(Car { color: "Blue", motor: Manual, roof: true, age: ("Used", 1000) })
Car order 2: Some(Car { color: "Green", motor: SemiAuto, roof: false, age: ("Used", 2000) })
Car order 3: Some(Car { color: "Red", motor: Automatic, roof: true, age: ("New", 0) })
Car order 4: Some(Car { color: "Silver", motor: SemiAuto, roof: false, age: ("New", 0) })
Car order 5: Some(Car { color: "Blue", motor: Manual, roof: true, age: ("Used", 3000) })
Car order 6: Some(Car { color: "Green", motor: Automatic, roof: true, age: ("Used", 4000) })

改訂されたコードの出力が異なることに注意してください。println!マクロディスプレイの内容Car各値を示すことによって、構造体と対応するフィールド名。

次の演習では、ループ式を使用してコードの冗長性を減らします。

for、while、およびloop式を使用します


多くの場合、プログラムには、その場で繰り返す必要のあるコードのブロックがあります。ループ式を使用して、繰り返しの実行方法をプログラムに指示できます。電話帳のすべてのエントリを印刷するには、ループ式を使用して、最初のエントリから最後のエントリまで印刷する方法をプログラムに指示できます。

Rustは、プログラムにコードのブロックを繰り返させるための3つのループ式を提供します。

  • loop:手動停止が発生しない限り、繰り返します。
  • while:条件が真のままで繰り返します。
  • for:コレクション内のすべての値に対して繰り返します。

この単元では、これらの各ループ式を見ていきます。

ループし続けるだけ

loop式は、無限ループを作成します。このキーワードを使用すると、式の本文でアクションを継続的に繰り返すことができます。ループを停止させるための直接アクションを実行するまで、アクションが繰り返されます。

次の例では、「We loopforever!」というテキストを出力します。そしてそれはそれ自体で止まりません。println!アクションは繰り返し続けます。

loop {
    println!("We loop forever!");
}

loop式を使用する場合、ループを停止する唯一の方法は、プログラマーとして直接介入する場合です。特定のコードを追加してループを停止したり、Ctrl + Cなどのキーボード命令を入力してプログラムの実行を停止したりできます。

loop式を停止する最も一般的な方法は、breakキーワードを使用してブレークポイントを設定することです。

loop {
    // Keep printing, printing, printing...
    println!("We loop forever!");
    // On the other hand, maybe we should stop!
    break;                            
}

プログラムがbreakキーワードを検出すると、loop式の本体でアクションの実行を停止し、次のコードステートメントに進みます。

breakキーワードは、特別な機能を明らかにするloop表現を。breakキーワードを使用すると、式本体でのアクションの繰り返しを停止することも、ブレークポイントで値を返すこともできます。

次の例はbreakloop式でキーワードを使用して値も返す方法を示しています。

let mut counter = 1;
// stop_loop is set when loop stops
let stop_loop = loop {
    counter *= 2;
    if counter > 100 {
        // Stop loop, return counter value
        break counter;
    }
};
// Loop should break when counter = 128
println!("Break the loop at counter = {}.", stop_loop);

出力は次のとおりです。

Break the loop at counter = 128.

私たちのloop表現の本体は、これらの連続したアクションを実行します。

  1. stop_loop変数を宣言します。
  2. 変数値をloop式の結果にバインドするようにプログラムに指示します。
  3. ループを開始します。loop式の本体でアクションを実行します:
    ループ本体
    1. counter値を現在の値の2倍にインクリメントします。
    2. counter値を確認してください。
    3. もしcounter値が100以上です。

ループから抜け出し、counter値を返します。

4. もしcounter値が100以上ではありません。

ループ本体でアクションを繰り返します。

5. stop_loop値を式のcounter結果である値に設定しますloop

loop式本体は、複数のブレークポイントを持つことができます。式に複数のブレークポイントがある場合、すべてのブレークポイントは同じタイプの値を返す必要があります。すべての値は、整数型、文字列型、ブール型などである必要があります。ブレークポイントが明示的に値を返さない場合、プログラムは式の結果を空のタプルとして解釈します()

しばらくループする

whileループは、条件式を使用しています。条件式が真である限り、ループが繰り返されます。このキーワードを使用すると、条件式がfalseになるまで、式本体のアクションを実行できます。

whileループは、ブール条件式を評価することから始まります。条件式がと評価されるtrueと、本体のアクションが実行されます。アクションが完了すると、制御は条件式に戻ります。条件式がと評価されるfalseと、while式は停止します。

次の例では、「しばらくループします...」というテキストを出力します。ループを繰り返すたびに、「カウントが5未満である」という条件がテストされます。条件が真のままである間、式本体のアクションが実行されます。条件が真でなくなった後、whileループは停止し、プログラムは次のコードステートメントに進みます。

while counter < 5 {
    println!("We loop a while...");
    counter = counter + 1;
}

これらの値のループ

forループは、項目のコレクションを処理するためにイテレータを使用しています。ループは、コレクション内の各アイテムの式本体のアクションを繰り返します。このタイプのループの繰り返しは、反復と呼ばれます。すべての反復が完了すると、ループは停止します。

Rustでは、配列、ベクトル、ハッシュマップなど、任意のコレクションタイプを反復処理できます。Rustはイテレータを使用して、コレクション内の各アイテムを最初から最後まで移動します

forループはイテレータとして一時変数を使用しています。変数はループ式の開始時に暗黙的に宣言され、現在の値は反復ごとに設定されます。

次のコードでは、コレクションはbig_birds配列であり、イテレーターの名前はbirdです。

let big_birds = ["ostrich", "peacock", "stork"];
for bird in big_birds

iter()メソッドを使用して、コレクション内のアイテムにアクセスします。for式は結果にイテレータの現在の値をバインドするiter()方法。式本体では、イテレータ値を操作できます。

let big_birds = ["ostrich", "peacock", "stork"];
for bird in big_birds.iter() {
    println!("The {} is a big bird.", bird);
}

出力は次のとおりです。

The ostrich is a big bird.
The peacock is a big bird.
The stork is a big bird.

イテレータを作成するもう1つの簡単な方法は、範囲表記を使用することですa..b。イテレータはa値から始まりb、1ステップずつ続きますが、値を使用しませんb

for number in 0..5 {
    println!("{}", number * 2);
}

このコードは、0、1、2、3、および4の数値をnumber繰り返し処理します。ループの繰り返しごとに、値を変数にバインドします。

出力は次のとおりです。

0
2
4
6
8

このコードを実行して、このRustPlaygroundでループを探索できます。

演習:ループを使用してデータを反復処理する


この演習では、自動車工場のプログラムを変更して、ループを使用して自動車の注文を反復処理します。

main関数を更新して、注文の完全なセットを処理するためのループ式を追加します。ループ構造は、コードの冗長性を減らすのに役立ちます。コードを簡素化することで、注文量を簡単に増やすことができます。

このcar_factory関数では、範囲外の値での実行時のパニックを回避するために、別のループを追加します。

課題は、サンプルコードを完成させて、コンパイルして実行することです。

この演習のサンプルコードで作業するには、次の2つのオプションがあります。

  • コードをコピーして、ローカル開発環境で編集します。
  • 準備されたRustPlaygroundでコードを開きます。

ノート

サンプルコードで、todo!マクロを探します。このマクロは、完了するか更新する必要があるコードを示します。

プログラムをロードする

前回の演習でプログラムコードを閉じた場合は、この準備されたRustPlaygroundでコードを再度開くことができます。

必ずプログラムを再構築し、コンパイラエラーなしで実行されることを確認してください。

ループ式でアクションを繰り返す

より多くの注文をサポートするには、プログラムを更新する必要があります。現在のコード構造では、冗長ステートメントを使用して6つの注文をサポートしています。冗長性は扱いにくく、維持するのが困難です。

ループ式を使用してアクションを繰り返し、各注文を作成することで、構造を単純化できます。簡略化されたコードを使用すると、多数の注文をすばやく作成できます。

  1. ではmain機能、削除次の文を。このコードブロックは、order変数を定義および設定し、自動車の注文のcar_factory関数とprintln!マクロを呼び出し、各注文をordersハッシュマップに挿入します。
// Order 6 cars
    // - Increment "order" after each request
    // - Add each order <K, V> pair to "orders" hash map
    // - Call println! to show order details from the hash map

    // Initialize order variable
    let mut order = 1;

    // Car order #1: Used, Hard top
    car = car_factory(order, 1000);
    orders.insert(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

    ...

    // Car order #6: Used, Hard top
    order = order + 1;
    car = car_factory(order, 4000);
    orders.insert(order, car);
    println!("Car order {}: {:?}", order, orders.get(&order));

2. 削除されたステートメントを次のコードブロックに置き換えます。

// Start with zero miles
    let mut miles = 0;

    todo!("Add a loop expression to fulfill orders for 6 cars, initialize `order` variable to 1") {

        // Call car_factory to fulfill order
        // Add order <K, V> pair to "orders" hash map
        // Call println! to show order details from the hash map        
        car = car_factory(order, miles);
        orders.insert(order, car);
        println!("Car order {}: {:?}", order, orders.get(&order));

        // Reset miles for order variety
        if miles == 2100 {
            miles = 0;
        } else {
            miles = miles + 700;
        }
    }

3. アクションを繰り返すループ式を追加して、6台の車の注文を作成します。order1に初期化された変数が必要です。

4. プログラムをビルドします。コードがエラーなしでコンパイルされることを確認してください。

次の例のような出力が表示されます。

Car order 1: Some(Car { color: "Blue", motor: Manual, roof: true, age: ("New", 0) })
Car order 2: Some(Car { color: "Green", motor: SemiAuto, roof: false, age: ("Used", 700) })
Car order 3: Some(Car { color: "Red", motor: Automatic, roof: true, age: ("Used", 1400) })
Car order 4: Some(Car { color: "Silver", motor: SemiAuto, roof: false, age: ("Used", 2100) })
Car order 5: Some(Car { color: "Blue", motor: Manual, roof: true, age: ("New", 0) })
Car order 6: Some(Car { color: "Green", motor: Automatic, roof: true, age: ("Used", 700) })

車の注文を11に増やす

 プログラムは現在、ループを使用して6台の車の注文を処理しています。6台以上注文するとどうなりますか?

  1. main関数のループ式を更新して、11台の車を注文します。
    todo!("Update the loop expression to create 11 cars");

2. プログラムを再構築します。実行時に、プログラムはパニックになります!

Compiling playground v0.0.1 (/playground)
    Finished dev [unoptimized + debuginfo] target(s) in 1.26s
    Running `target/debug/playground`
thread 'main' panicked at 'index out of bounds: the len is 4 but the index is 4', src/main.rs:34:29

この問題を解決する方法を見てみましょう。

ループ式で実行時のパニックを防ぐ

このcar_factory関数では、if / else式を使用colorして、colors配列のインデックスの値を確認します。

// Prevent panic: Check color index for colors array, reset as needed
    // Valid color = 1, 2, 3, or 4
    // If color > 4, reduce color to valid index
    let mut color = order as usize;
    if color > 4 {        
        // color = 5 --> index 1, 6 --> 2, 7 --> 3, 8 --> 4
        color = color - 4;
    }

colors配列には4つの要素を持ち、かつ有効なcolor場合は、インデックスの範囲は0〜3の条件式をチェックしているcolor私たちはをチェックしません(インデックスが4よりも大きい場合color、その後の関数で4に等しいインデックスへのときに我々のインデックスを車の色を割り当てる配列では、インデックス値から1を減算しますcolor - 1color値4はcolors[3]、配列と同様に処理されます。)

現在のif / else式は、8台以下の車を注文するときの実行時のパニックを防ぐためにうまく機能します。しかし、11台の車を注文すると、プログラムは9番目の注文でパニックになります。より堅牢になるように式を調整する必要があります。この改善を行うために、別のループ式を使用します。

  1. ではcar_factory機能、ループ式であれば/他の条件文を交換してください。colorインデックス値が4より大きい場合に実行時のパニックを防ぐために、次の擬似コードステートメントを修正してください。
// Prevent panic: Check color index, reset as needed
    // If color = 1, 2, 3, or 4 - no change needed
    // If color > 4, reduce to color to a valid index
    let mut color = order as usize;
    todo!("Replace `if/else` condition with a loop to prevent run-time panic for color > 4");

ヒント

この場合、if / else条件からループ式への変更は実際には非常に簡単です。

2. プログラムをビルドします。コードがエラーなしでコンパイルされることを確認してください。

次の出力が表示されます。

Car order 1: Some(Car { color: "Blue", motor: Manual, roof: true, age: ("New", 0) })
Car order 2: Some(Car { color: "Green", motor: SemiAuto, roof: false, age: ("Used", 700) })
Car order 3: Some(Car { color: "Red", motor: Automatic, roof: true, age: ("Used", 1400) })
Car order 4: Some(Car { color: "Silver", motor: SemiAuto, roof: false, age: ("Used", 2100) })
Car order 5: Some(Car { color: "Blue", motor: Manual, roof: true, age: ("New", 0) })
Car order 6: Some(Car { color: "Green", motor: Automatic, roof: true, age: ("Used", 700) })
Car order 7: Some(Car { color: "Red", motor: Manual, roof: true, age: ("Used", 1400) })
Car order 8: Some(Car { color: "Silver", motor: SemiAuto, roof: false, age: ("Used", 2100) })
Car order 9: Some(Car { color: "Blue", motor: Automatic, roof: true, age: ("New", 0) })
Car order 10: Some(Car { color: "Green", motor: SemiAuto, roof: false, age: ("Used", 700) })
Car order 11: Some(Car { color: "Red", motor: Manual, roof: true, age: ("Used", 1400) })

概要

このモジュールでは、Rustで使用できるさまざまなループ式を調べ、ハッシュマップの操作方法を発見しました。データは、キーと値のペアとしてハッシュマップに保存されます。ハッシュマップは拡張可能です。

loop手動でプロセスを停止するまでの式は、アクションを繰り返します。while式をループして、条件が真である限りアクションを繰り返すことができます。このfor式は、データ収集を反復処理するために使用されます。

この演習では、自動車プログラムを拡張して、繰り返されるアクションをループし、すべての注文を処理しました。注文を追跡するためにハッシュマップを実装しました。

このラーニングパスの次のモジュールでは、Rustコードでエラーと障害がどのように処理されるかについて詳しく説明します。

 リンク: https://docs.microsoft.com/en-us/learn/modules/rust-loop-expressions/

#rust #Beginners 

ERIC  MACUS

ERIC MACUS

1647540000

Substrate Knowledge Map For Hackathon Participants

Substrate Knowledge Map for Hackathon Participants

The Substrate Knowledge Map provides information that you—as a Substrate hackathon participant—need to know to develop a non-trivial application for your hackathon submission.

The map covers 6 main sections:

  1. Introduction
  2. Basics
  3. Preliminaries
  4. Runtime Development
  5. Polkadot JS API
  6. Smart Contracts

Each section contains basic information on each topic, with links to additional documentation for you to dig deeper. Within each section, you'll find a mix of quizzes and labs to test your knowledge as your progress through the map. The goal of the labs and quizzes is to help you consolidate what you've learned and put it to practice with some hands-on activities.

Introduction

One question we often get is why learn the Substrate framework when we can write smart contracts to build decentralized applications?

The short answer is that using the Substrate framework and writing smart contracts are two different approaches.

Smart contract development

Traditional smart contract platforms allow users to publish additional logic on top of some core blockchain logic. Since smart contract logic can be published by anyone, including malicious actors and inexperienced developers, there are a number of intentional safeguards and restrictions built around these public smart contract platforms. For example:

Fees: Smart contract developers must ensure that contract users are charged for the computation and storage they impose on the computers running their contract. With fees, block creators are protected from abuse of the network.

Sandboxed: A contract is not able to modify core blockchain storage or storage items of other contracts directly. Its power is limited to only modifying its own state, and the ability to make outside calls to other contracts or runtime functions.

Reversion: Contracts can be prone to undesirable situations that lead to logical errors when wanting to revert or upgrade them. Developers need to learn additional patterns such as splitting their contract's logic and data to ensure seamless upgrades.

These safeguards and restrictions make running smart contracts slower and more costly. However, it's important to consider the different developer audiences for contract development versus Substrate runtime development.

Building decentralized applications with smart contracts allows your community to extend and develop on top of your runtime logic without worrying about proposals, runtime upgrades, and so on. You can also use smart contracts as a testing ground for future runtime changes, but done in an isolated way that protects your network from any errors the changes might introduce.

In summary, smart contract development:

  • Is inherently safer to the network.
  • Provides economic incentives and transaction fee mechanisms that can't be directly controlled by the smart contract author.
  • Provides computational overhead to support graceful logical failures.
  • Has a low barrier to entry for developers and enables a faster pace of community interaction.

Substrate runtime development

Unlike traditional smart contract development, Substrate runtime development offers none of the network protections or safeguards. Instead, as a runtime developer, you have total control over how the blockchain behaves. However, this level of control also means that there is a higher barrier to entry.

Substrate is a framework for building blockchains, which almost makes comparing it to smart contract development like comparing apples and oranges. With the Substrate framework, developers can build smart contracts but that is only a fraction of using Substrate to its full potential.

With Substrate, you have full control over the underlying logic that your network's nodes will run. You also have full access for modifying and controlling each and every storage item across your runtime modules. As you progress through this map, you'll discover concepts and techniques that will help you to unlock the potential of the Substrate framework, giving you the freedom to build the blockchain that best suits the needs of your application.

You'll also discover how you can upgrade the Substrate runtime with a single transaction instead of having to organize a community hard-fork. Upgradeability is one of the primary design features of the Substrate framework.

In summary, runtime development:

  • Provides low level access to your entire blockchain.
  • Removes the overhead of built-in safety for performance.
  • Has a higher barrier of entry for developers.
  • Provides flexibility to customize full-stack application logic.

To learn more about using smart contracts within Substrate, refer to the Smart Contract - Overview page as well as the Polkadot Builders Guide.

Navigating the documentation

If you need any community support, please join the following channels based on the area where you need help:

Alternatively, also look for support on Stackoverflow where questions are tagged with "substrate" or on the Parity Subport repo.

Use the following links to explore the sites and resources available on each:

Substrate Developer Hub has the most comprehensive all-round coverage about Substrate, from a "big picture" explanation of architecture to specific technical concepts. The site also provides tutorials to guide you as your learn the Substrate framework and the API reference documentation. You should check this site first if you want to look up information about Substrate runtime development. The site consists of:

Knowledge Base: Explaining the foundational concepts of building blockchain runtimes using Substrate.

Tutorials: Hand-on tutorials for developers to follow. The first SIX tutorials show the fundamentals in Substrate and are recommended for every Substrate learner to go through.

How-to Guides: These resources are like the O'Reilly cookbook series written in a task-oriented way for readers to get the job done. Some examples of the topics overed include:

  • Setting up proper weight functions for extrinsic calls.
  • Using off-chain workers to fetch HTTP requests.
  • Writing tests for your pallets It can also be read from

API docs: Substrate API reference documentation.

Substrate Node Template provides a light weight, minimal Substrate blockchain node that you can set up as a local development environment.

Substrate Front-end template provides a front-end interface built with React using Polkadot-JS API to connect to any Substrate node. Developers are encouraged to start new Substrate projects based on these templates.

If you face any technical difficulties and need support, feel free to join the Substrate Technical matrix channel and ask your questions there.

Additional resources

Polkadot Wiki documents the specific behavior and mechanisms of the Polkadot network. The Polkadot network allows multiple blockchains to connect and pass messages to each other. On the wiki, you can learn about how Polkadot—built using Substrate—is customized to support inter-blockchain message passing.

Polkadot JS API doc: documents how to use the Polkadot-JS API. This JavaScript-based API allows developers to build custom front-ends for their blockchains and applications. Polkadot JS API provides a way to connect to Substrate-based blockchains to query runtime metadata and send transactions.

Quiz #1

👉 Submit your answers to Quiz #1

Basics

Set up your local development environment

Here you will set up your local machine to install the Rust compiler—ensuring that you have both stable and nightly versions installed. Both stable and nightly versions are required because currently a Substrate runtime is compiled to a native binary using the stable Rust compiler, then compiled to a WebAssembly (WASM) binary, which only the nightly Rust compiler can do.

Also refer to:

Lab #1

👉 Complete Lab #1: Run a Substrate node

Interact with a Substrate network using Polkadot-JS apps

Polkadot JS Apps is the canonical front-end to interact with any Substrate-based chain.

You can configure whichever endpoint you want it to connected to, even to your localhost running node. Refer to the following two diagrams.

  1. Click on the top left side showing your currently connected network:

assets/01-polkadot-app-endpoint.png

  1. Scroll to the bottom of the menu, open DEVELOPMENT, and choose either Local Node or Custom to specify your own endpoint.

assets/02-polkadot-app-select-endpoint.png

Quiz #2

👉 Complete Quiz #2

Lab #2

👉 Complete Lab #2: Using Polkadot-JS Apps

Notes: If you are connecting Apps to a custom chain (or your locally-running node), you may need to specify your chain's custom data types in JSON under Settings > Developer.

Polkadot-JS Apps only receives a series of bytes from the blockchain. It is up to the developer to tell it how to decode and interpret these custom data type. To learn more on this, refer to:

You will also need to create an account. To do so, follow these steps on account generation. You'll learn that you can also use the Polkadot-JS Browser Plugin (a Metamask-like browser extension to manage your Substrate accounts) and it will automatically be imported into Polkadot-JS Apps.

Notes: When you run a Substrate chain in development mode (with the --dev flag), well-known accounts (Alice, Bob, Charlie, etc.) are always created for you.

Lab #3

👉 Complete Lab #3: Create an Account

Preliminaries

You need to know some Rust programming concepts and have a good understanding on how blockchain technology works in order to make the most of developing with Substrate. The following resources will help you brush up in these areas.

Rust

You will need familiarize yourself with Rust to understand how Substrate is built and how to make the most of its capabilities.

If you are new to Rust, or need a brush up on your Rust knowledge, please refer to The Rust Book. You could still continue learning about Substrate without knowing Rust, but we recommend you come back to this section whenever in doubt about what any of the Rust syntax you're looking at means. Here are the parts of the Rust book we recommend you familiarize yourself with:

  • ch 1 - 10: These chapters cover the foundational knowledge of programming in Rust
  • ch 13: On iterators and closures
  • ch 18 - 19: On advanced traits and advanced types. Learn a bit about macros as well. You will not necessarily be writing your own macros, but you'll be using a lot of Substrate and FRAME's built-in macros to write your blockchain runtime.

How blockchains work

Given that you'll be writing a blockchain runtime, you need to know what a blockchain is, and how it works. The **Web3 Blockchain Fundamental MOOC Youtube video series provides a good basis for understanding key blockchain concepts and how blockchains work.

The lectures we recommend you watch are: lectures 1 - 7 and lecture 10. That's 8 lectures, or about 4 hours of video.

Quiz #3

👉 Complete Quiz #3

Substrate runtime development

High level architecture

To know more about the high level architecture of Substrate, please go through the Knowledge Base articles on Getting Started: Overview and Getting Started: Architecture.

In this document, we assume you will develop a Substrate runtime with FRAME (v2). This is what a Substrate node consists of.

assets/03-substrate-architecture.png

Each node has many components that manage things like the transaction queue, communicating over a P2P network, reaching consensus on the state of the blockchain, and the chain's actual runtime logic (aka the blockchain runtime). Each aspect of the node is interesting in its own right, and the runtime is particularly interesting because it contains the business logic (aka "state transition function") that codifies the chain's functionality. The runtime contains a collection of pallets that are configured to work together.

On the node level, Substrate leverages libp2p for the p2p networking layer and puts the transaction pool, consensus mechanism, and underlying data storage (a key-value database) on the node level. These components all work "under the hood", and in this knowledge map we won't cover them in detail except for mentioning their existence.

Quiz #4

👉 Complete Quiz #4

Runtime development topics

In our Developer Hub, we have a thorough coverage on various subjects you need to know to develop with Substrate. So here we just list out the key topics and reference back to Developer Hub. Please go through the following key concepts and the directed resources to know the fundamentals of runtime development.

Key Concept: Runtime, this is where the blockchain state transition function (the blockchain application-specific logic) is defined. It is about composing multiple pallets (can be understood as Rust modules) together in the runtime and hooking them up together.

Runtime Development: Execution, this article describes how a block is produced, and how transactions are selected and executed to reach the next "stage" in the blockchain.

Runtime Develpment: Pallets, this article describes what the basic structure of a Substrate pallet is consists of.

Runtime Development: FRAME, this article gives a high level overview of the system pallets Substrate already implements to help you quickly develop as a runtime engineer. Have a quick skim so you have a basic idea of the different pallets Substrate is made of.

Lab #4

👉 Complete Lab #4: Adding a Pallet into a Runtime

Runtime Development: Storage, this article describes how data is stored on-chain and how you could access them.

Runtime Development: Events & Errors, this page describe how external parties know what has happened in the blockchain, via the emitted events and errors when executing transactions.

Notes: All of the above concepts we leverage on the #[pallet::*] macro to define them in the code. If you are interested to learn more about what other types of pallet macros exist go to the FRAME macro API documentation and this doc on some frequently used Substrate macros.

Lab #5

👉 Complete Lab #5: Building a Proof-of-Existence dApp

Lab #6

👉 Complete Lab #6: Building a Substrate Kitties dApp

Quiz #5

👉 Complete Quiz #5

Polkadot JS API

Polkadot JS API is the javascript API for Substrate. By using it you can build a javascript front end or utility and interact with any Substrate-based blockchain.

The Substrate Front-end Template is an example of using Polkadot JS API in a React front-end.

  • Runtime Development: Metadata, this article describes the API allowing external parties to query what API is open for the chain. Polkadot JS API makes use of a chain's metadata to know what queries and functions are available from a chain to call.

Lab #7

👉 Complete Lab #7: Using Polkadot-JS API

Quiz #6

👉 Complete Quiz #6: Using Polkadot-JS API

Smart contracts

Learn about the difference between smart contract development vs Substrate runtime development, and when to use each here.

In Substrate, you can program smart contracts using ink!.

Quiz #7

👉 Complete Quiz #7: Using ink!

What we do not cover

A lot 😄

On-chain runtime upgrades. We have a tutorial on On-chain (forkless) Runtime Upgrade. This tutorial introduces how to perform and schedule a runtime upgrade as an on-chain transaction.

About transaction weight and fee, and benchmarking your runtime to determine the proper transaction cost.

Off-chain Features

There are certain limits to on-chain logic. For instance, computation cannot be too intensive that it affects the block output time, and computation must be deterministic. This means that computation that relies on external data fetching cannot be done on-chain. In Substrate, developers can run these types of computation off-chain and have the result sent back on-chain via extrinsics.

Tightly- and Loosely-coupled pallets, calling one pallet's functions from another pallet via trait specification.

Blockchain Consensus Mechansim, and a guide on customizing it to proof-of-work here.

Parachains: one key feature of Substrate is the capability of becoming a parachain for relay chains like Polkadot. You can develop your own application-specific logic in your chain and rely on the validator community of the relay chain to secure your network, instead of building another validator community yourself. Learn more with the following resources:

Terms clarification

  • Substrate: the blockchain development framework built for writing highly customized, domain-specific blockchains.
  • Polkadot: Polkadot is the relay chain blockchain, built with Substrate.
  • Kusama: Kusama is Polkadot's canary network, used to launch features before these features are launched on Polkadot. You could view it as a beta-network with real economic value where the state of the blockchain is never reset.
  • Web 3.0: is the decentralized internet ecosystem that, instead of apps being centrally stored in a few servers and managed by a sovereign party, it is an open, trustless, and permissionless network when apps are not controlled by a centralized entity.
  • Web3 Foundation: A foundation setup to support the development of decentralized web software protocols. Learn more about what they do on thier website.

Others


Author: substrate-developer-hub
Source Code: https://github.com/substrate-developer-hub/hackathon-knowledge-map
License: 

#blockchain #substrate 

Cree Una Aplicación De Android Con El Marco Kivy Python

Si es un desarrollador de Python que está pensando en comenzar con el desarrollo móvil, entonces el marco Kivy es su mejor opción. Con Kivy, puede desarrollar aplicaciones independientes de la plataforma que compilan para iOS, Android, Windows, macOS y Linux. En este artículo, cubriremos Android específicamente porque es el más utilizado.

Construiremos una aplicación generadora de números aleatorios simple que puede instalar en su teléfono y probar cuando haya terminado. Para continuar con este artículo, debe estar familiarizado con Python. ¡Empecemos!

Primeros pasos con Kivy

Primero, necesitará un nuevo directorio para su aplicación. Asegúrese de tener Python instalado en su máquina y abra un nuevo archivo de Python. Deberá instalar el módulo Kivy desde su terminal usando cualquiera de los comandos a continuación. Para evitar conflictos de paquetes, asegúrese de instalar Kivy en un entorno virtual:

pip install kivy 
//
pip3 install kivy 

Una vez que haya instalado Kivy, debería ver un mensaje de éxito de su terminal que se parece a las capturas de pantalla a continuación:

Instalación decepcionada

Instalación exitosa de Kivy

 

A continuación, navegue a la carpeta de su proyecto. En el main.pyarchivo, necesitaremos importar el módulo Kivy y especificar qué versión queremos. Puede usar Kivy v2.0.0, pero si tiene un teléfono inteligente anterior a Android 8.0, le recomiendo usar Kivy v1.9.0. Puede jugar con las diferentes versiones durante la compilación para ver las diferencias en las características y el rendimiento.

Agregue el número de versión justo después de la import kivylínea de la siguiente manera:

kivy.require('1.9.0')

Ahora, crearemos una clase que básicamente definirá nuestra aplicación; Voy a nombrar el mío RandomNumber. Esta clase heredará la appclase de Kivy. Por lo tanto, debe importar appagregando from kivy.app import App:

class RandomNumber(App): 

En la RandomNumberclase, deberá agregar una función llamada build, que toma un selfparámetro. Para devolver la interfaz de usuario, usaremos la buildfunción. Por ahora, lo tengo devuelto como una simple etiqueta. Para hacerlo, deberá importar Labelusando la línea from kivy.uix.label import Label:

import kivy
from kivy.app import App
from kivy.uix.label import Label

class RandomNumber(App):
  def build(self):
    return Label(text="Random Number Generator")

¡Ahora, el esqueleto de nuestra aplicación está completo! Antes de continuar, debe crear una instancia de la RandomNumberclase y ejecutarla en su terminal o IDE para ver la interfaz:

importar kivy de kivy.app importar aplicación de kivy.uix.label clase de etiqueta de importación RandomNumber(App): def build(self): return Label(text="Generador de números aleatorios") randomApp = RandomNumber() randomApp.run()

Cuando ejecuta la instancia de clase con el texto Random Number Generator, debería ver una interfaz o ventana simple que se parece a la siguiente captura de pantalla:

 

Interfaz simple después de ejecutar el código.

No podrá ejecutar el texto en Android hasta que haya terminado de construir todo.

Externalización de la interfaz

A continuación, necesitaremos una forma de subcontratar la interfaz. Primero, crearemos un archivo Kivy en nuestro directorio que albergará la mayor parte de nuestro trabajo de diseño. Querrá nombrar este archivo con el mismo nombre que su clase usando letras minúsculas y una .kvextensión. Kivy asociará automáticamente el nombre de la clase y el nombre del archivo, pero es posible que no funcione en Android si son exactamente iguales.

Dentro de ese .kvarchivo, debe especificar el diseño de su aplicación, incluidos elementos como la etiqueta, los botones, los formularios, etc. Para simplificar esta demostración, agregaré una etiqueta para el título Random Number, una etiqueta que servirá como marcador de posición. para el número aleatorio que se genera _, y un Generatebotón que llama a la generatefunción.

Mi .kvarchivo se parece al siguiente código, pero puede jugar con los diferentes valores para que se ajusten a sus requisitos:

<boxLayout>:
    orientation: "vertical"
    Label:
        text: "Random Number"
        font_size: 30
        color: 0, 0.62, 0.96

    Label:
        text: "_"
        font_size: 30

    Button:
        text: "Generate"
        font_size: 15 

En el main.pyarchivo, ya no necesita la Labeldeclaración de importación porque el archivo Kivy se encarga de su interfaz de usuario. Sin embargo, necesita importar boxlayout, que utilizará en el archivo Kivy.

En su archivo principal, debe agregar la declaración de importación y editar su main.pyarchivo para leer return BoxLayout()el buildmétodo:

from kivy.uix.boxlayout import BoxLayout

Si ejecuta el comando anterior, debería ver una interfaz simple que tiene el título del número aleatorio, el _marcador de posición y el generatebotón en el que se puede hacer clic:

Aplicación de números aleatorios renderizada

Tenga en cuenta que no tuvo que importar nada para que funcione el archivo Kivy. Básicamente, cuando ejecuta la aplicación, regresa boxlayoutbuscando un archivo dentro del archivo Kivy con el mismo nombre que su clase. Tenga en cuenta que esta es una interfaz simple y puede hacer que su aplicación sea tan robusta como desee. Asegúrese de consultar la documentación del idioma Kv .

Generar la función de números aleatorios

Ahora que nuestra aplicación está casi terminada, necesitaremos una función simple para generar números aleatorios cuando un usuario haga clic en el generatebotón y luego mostrar ese número aleatorio en la interfaz de la aplicación. Para hacerlo, necesitaremos cambiar algunas cosas en nuestros archivos.

Primero, importaremos el módulo que usaremos para generar un número aleatorio con import random. Luego, crearemos una función o método que llame al número generado. Para esta demostración, usaré un rango entre 0y 2000. Generar el número aleatorio es simple con el random.randint(0, 2000)comando. Agregaremos esto a nuestro código en un momento.

A continuación, crearemos otra clase que será nuestra propia versión del box layout. Nuestra clase tendrá que heredar la box layoutclase, que alberga el método para generar números aleatorios y representarlos en la interfaz:

class MyRoot(BoxLayout):
    def __init__(self):
        super(MyRoot, self).__init__()

Dentro de esa clase, crearemos el generatemétodo, que no solo generará números aleatorios, sino que también manipulará la etiqueta que controla lo que se muestra como número aleatorio en el archivo Kivy.

Para acomodar este método, primero necesitaremos hacer cambios en el .kvarchivo. Dado que la MyRootclase ha heredado el box layout, puede crear MyRootel elemento de nivel superior en su .kvarchivo:

<MyRoot>:
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15

Tenga en cuenta que todavía mantiene todas las especificaciones de la interfaz de usuario con sangría en el archivo Box Layout. Después de esto, debe agregar una identificación a la etiqueta que contendrá los números generados, lo que facilita la manipulación cuando generatese llama a la función. Debe especificar la relación entre la ID en este archivo y otra en el código principal en la parte superior, justo antes de la BoxLayoutlínea:

<MyRoot>:
    random_label: random_label
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            id: random_label
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15

La random_label: random_labellínea básicamente significa que la etiqueta con el ID random_labelse asignará a random_labelen el main.pyarchivo, lo que significa que cualquier acción que manipula random_labelserán mapeados en la etiqueta con el nombre especificado.

Ahora podemos crear el método para generar el número aleatorio en el archivo principal:

def generate_number(self):
    self.random_label.text = str(random.randint(0, 2000))

# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.

La MyRootclase debería parecerse al siguiente código:

class MyRoot(BoxLayout):
    def __init__(self):
        super(MyRoot, self).__init__()

    def generate_number(self):
        self.random_label.text = str(random.randint(0, 2000))

¡Felicidades! Ya ha terminado con el archivo principal de la aplicación. Lo único que queda por hacer es asegurarse de llamar a esta función cuando se haga generateclic en el botón. Solo necesita agregar la línea on_press: root.generate_number()a la parte de selección de botones de su .kvarchivo:

<MyRoot>:
    random_label: random_label
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            id: random_label
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15
            on_press: root.generate_number()

Ahora, puede ejecutar la aplicación.

Compilando nuestra aplicación en Android

Antes de compilar nuestra aplicación en Android, tengo malas noticias para los usuarios de Windows. Necesitará Linux o macOS para compilar su aplicación de Android. Sin embargo, no necesita tener una distribución de Linux separada, en su lugar, puede usar una máquina virtual.

Para compilar y generar una .apkaplicación Android completa , usaremos una herramienta llamada Buildozer . Instalemos Buildozer a través de nuestra terminal usando uno de los siguientes comandos:

pip3 install buildozer
//
pip install buildozer

Ahora, instalaremos algunas de las dependencias requeridas de Buildozer. Estoy en Linux Ergo, así que usaré comandos específicos de Linux. Debe ejecutar estos comandos uno por uno:

sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev

pip3 install --upgrade Cython==0.29.19 virtualenv 

# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/

Después de ejecutar los comandos específicos, ejecute buildozer init. Debería ver un resultado similar a la captura de pantalla a continuación:

Inicialización exitosa de Buildozer

El comando anterior crea un .specarchivo Buildozer , que puede usar para hacer especificaciones para su aplicación, incluido el nombre de la aplicación, el ícono, etc. El .specarchivo debe verse como el bloque de código a continuación:

[app]

# (str) Title of your application
title = My Application

# (str) Package name
package.name = myapp

# (str) Package domain (needed for android/ios packaging)
package.domain = org.test

# (str) Source code where the main.py live
source.dir = .

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas

# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png

# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec

# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin

# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg

# (str) Application versioning (method 1)
version = 0.1

# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py

# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy

# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy

# (list) Garden requirements
#garden_requirements =

# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png

# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png

# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait

# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY

#
# OSX Specific
#

#
# author = © Copyright Info

# change the major version of python used by the app
osx.python_version = 3

# Kivy version to use
osx.kivy_version = 1.9.1

#
# Android specific
#

# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0

# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF

# (list) Permissions
#android.permissions = INTERNET

# (int) Target Android API, should be as high as possible.
#android.api = 27

# (int) Minimum API your APK will support.
#android.minapi = 21

# (int) Android SDK version to use
#android.sdk = 20

# (str) Android NDK version to use
#android.ndk = 19b

# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21

# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True

# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =

# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =

# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =

# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False

# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False

# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity

# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"

# (list) Pattern to whitelist for the whole project
#android.whitelist =

# (str) Path to a custom whitelist file
#android.whitelist_src =

# (str) Path to a custom blacklist file
#android.blacklist_src =

# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar

# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =

# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =

# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =

# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"

# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes 
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =

# (list) packaging options to add 
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes 
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =

# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity

# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME

# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png

# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =

# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard

# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so

# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False

# (list) Android application meta-data to set (key=value format)
#android.meta_data =

# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =

# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =

# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D

# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1

# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a

# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1

#
# Python for android (p4a) specific
#

# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy

# (str) python-for-android branch to use, defaults to master
#p4a.branch = master

# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =

# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =

# (str) Filename to the hook for p4a
#p4a.hook =

# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2

# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =


#
# iOS specific
#

# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master

# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0

# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"

# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s


[buildozer]

# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2

# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1

# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer

# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin

#    -----------------------------------------------------------------------------
#    List as sections
#
#    You can define all the "list" as [section:key].
#    Each line will be considered as a option to the list.
#    Let's take [app] / source.exclude_patterns.
#    Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
#    This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#


#    -----------------------------------------------------------------------------
#    Profiles
#
#    You can extend section / key with a profile
#    For example, you want to deploy a demo version of your application without
#    HD content. You could first change the title to add "(demo)" in the name
#    and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
#    Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

Si desea especificar cosas como el ícono, los requisitos, la pantalla de carga, etc., debe editar este archivo. Después de realizar todas las ediciones deseadas en su aplicación, ejecute buildozer -v android debugdesde el directorio de su aplicación para construir y compilar su aplicación. Esto puede llevar un tiempo, especialmente si tiene una máquina lenta.

Una vez finalizado el proceso, su terminal debería tener algunos registros, uno que confirme que la compilación fue exitosa:

Construcción exitosa de Android

También debe tener una versión APK de su aplicación en su directorio bin. Este es el ejecutable de la aplicación que instalará y ejecutará en su teléfono:

Android .apk en el directorio bin

Conclusión

¡Felicidades! Si ha seguido este tutorial paso a paso, debería tener una aplicación simple de generador de números aleatorios en su teléfono. Juega con él y ajusta algunos valores, luego reconstruye. Ejecutar la reconstrucción no llevará tanto tiempo como la primera compilación.

Como puede ver, crear una aplicación móvil con Python es bastante sencillo , siempre que esté familiarizado con el marco o módulo con el que está trabajando. Independientemente, la lógica se ejecuta de la misma manera.

Familiarícese con el módulo Kivy y sus widgets. Nunca se puede saber todo a la vez. Solo necesita encontrar un proyecto y mojarse los pies lo antes posible. Codificación feliz.

Enlace: https://blog.logrocket.com/build-android-application-kivy-python-framework/

#python 

Build an Android application with Kivy Python framework

If you’re a Python developer thinking about getting started with mobile development, then the Kivy framework is your best bet. With Kivy, you can develop platform-independent applications that compile for iOS, Android, Windows, macOS, and Linux. In this article, we’ll cover Android specifically because it is the most used.

We’ll build a simple random number generator app that you can install on your phone and test when you are done. To follow along with this article, you should be familiar with Python. Let’s get started!

Getting started with Kivy

First, you’ll need a new directory for your app. Make sure you have Python installed on your machine and open a new Python file. You’ll need to install the Kivy module from your terminal using either of the commands below. To avoid any package conflicts, be sure you’re installing Kivy in a virtual environment:

pip install kivy 
//
pip3 install kivy 

Once you have installed Kivy, you should see a success message from your terminal that looks like the screenshots below:

Kivy installation

Successful Kivy installation

 

Next, navigate into your project folder. In the main.py file, we’ll need to import the Kivy module and specify which version we want. You can use Kivy v2.0.0, but if you have a smartphone that is older than Android 8.0, I recommend using Kivy v1.9.0. You can mess around with the different versions during the build to see the differences in features and performance.

Add the version number right after the import kivy line as follows:

kivy.require('1.9.0')

Now, we’ll create a class that will basically define our app; I’ll name mine RandomNumber. This class will inherit the app class from Kivy. Therefore, you need to import the app by adding from kivy.app import App:

class RandomNumber(App): 

In the RandomNumber class, you’ll need to add a function called build, which takes a self parameter. To actually return the UI, we’ll use the build function. For now, I have it returned as a simple label. To do so, you’ll need to import Label using the line from kivy.uix.label import Label:

import kivy
from kivy.app import App
from kivy.uix.label import Label

class RandomNumber(App):
  def build(self):
    return Label(text="Random Number Generator")

Now, our app skeleton is complete! Before moving forward, you should create an instance of the RandomNumber class and run it in your terminal or IDE to see the interface:

import kivy from kivy.app import App from kivy.uix.label import Label class RandomNumber(App):  def build(self):    return Label(text="Random Number Generator") randomApp = RandomNumber() randomApp.run()

When you run the class instance with the text Random Number Generator, you should see a simple interface or window that looks like the screenshot below:

 

Simple interface after running the code

You won’t be able to run the text on Android until you’ve finished building the whole thing.

Outsourcing the interface

Next, we’ll need a way to outsource the interface. First, we’ll create a Kivy file in our directory that will house most of our design work. You’ll want to name this file the same name as your class using lowercase letters and a .kv extension. Kivy will automatically associate the class name and the file name, but it may not work on Android if they are exactly the same.

Inside that .kv file, you need to specify the layout for your app, including elements like the label, buttons, forms, etc. To keep this demonstration simple, I’ll add a label for the title Random Number, a label that will serve as a placeholder for the random number that is generated _, and a Generate button that calls the generate function.

My .kv file looks like the code below, but you can mess around with the different values to fit your requirements:

<boxLayout>:
    orientation: "vertical"
    Label:
        text: "Random Number"
        font_size: 30
        color: 0, 0.62, 0.96

    Label:
        text: "_"
        font_size: 30

    Button:
        text: "Generate"
        font_size: 15 

In the main.py file, you no longer need the Label import statement because the Kivy file takes care of your UI. However, you do need to import boxlayout, which you will use in the Kivy file.

In your main file, you need to add the import statement and edit your main.py file to read return BoxLayout() in the build method:

from kivy.uix.boxlayout import BoxLayout

If you run the command above, you should see a simple interface that has the random number title, the _ place holder, and the clickable generate button:

Random Number app rendered

Notice that you didn’t have to import anything for the Kivy file to work. Basically, when you run the app, it returns boxlayout by looking for a file inside the Kivy file with the same name as your class. Keep in mind, this is a simple interface, and you can make your app as robust as you want. Be sure to check out the Kv language documentation.

Generate the random number function

Now that our app is almost done, we’ll need a simple function to generate random numbers when a user clicks the generate button, then render that random number into the app interface. To do so, we’ll need to change a few things in our files.

First, we’ll import the module that we’ll use to generate a random number with import random. Then, we’ll create a function or method that calls the generated number. For this demonstration, I’ll use a range between 0 and 2000. Generating the random number is simple with the random.randint(0, 2000) command. We’ll add this into our code in a moment.

Next, we’ll create another class that will be our own version of the box layout. Our class will have to inherit the box layout class, which houses the method to generate random numbers and render them on the interface:

class MyRoot(BoxLayout):
    def __init__(self):
        super(MyRoot, self).__init__()

Within that class, we’ll create the generate method, which will not only generate random numbers but also manipulate the label that controls what is displayed as the random number in the Kivy file.

To accommodate this method, we’ll first need to make changes to the .kv file . Since the MyRoot class has inherited the box layout, you can make MyRoot the top level element in your .kv file:

<MyRoot>:
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15

Notice that you are still keeping all your UI specifications indented in the Box Layout. After this, you need to add an ID to the label that will hold the generated numbers, making it easy to manipulate when the generate function is called. You need to specify the relationship between the ID in this file and another in the main code at the top, just before the BoxLayout line:

<MyRoot>:
    random_label: random_label
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            id: random_label
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15

The random_label: random_label line basically means that the label with the ID random_label will be mapped to random_label in the main.py file, meaning that any action that manipulates random_label will be mapped on the label with the specified name.

We can now create the method to generate the random number in the main file:

def generate_number(self):
    self.random_label.text = str(random.randint(0, 2000))

# notice how the class method manipulates the text attributre of the random label by a# ssigning it a new random number generate by the 'random.randint(0, 2000)' funcion. S# ince this the random number generated is an integer, typecasting is required to make # it a string otherwise you will get a typeError in your terminal when you run it.

The MyRoot class should look like the code below:

class MyRoot(BoxLayout):
    def __init__(self):
        super(MyRoot, self).__init__()

    def generate_number(self):
        self.random_label.text = str(random.randint(0, 2000))

Congratulations! You’re now done with the main file of the app. The only thing left to do is make sure that you call this function when the generate button is clicked. You need only add the line on_press: root.generate_number() to the button selection part of your .kv file:

<MyRoot>:
    random_label: random_label
    BoxLayout:
        orientation: "vertical"
        Label:
            text: "Random Number"
            font_size: 30
            color: 0, 0.62, 0.96

        Label:
            id: random_label
            text: "_"
            font_size: 30

        Button:
            text: "Generate"
            font_size: 15
            on_press: root.generate_number()

Now, you can run the app.

Compiling our app on Android

Before compiling our app on Android, I have some bad news for Windows users. You’ll need Linux or macOS to compile your Android application. However, you don’t need to have a separate Linux distribution, instead, you can use a virtual machine.

To compile and generate a full Android .apk application, we’ll use a tool called Buildozer. Let’s install Buildozer through our terminal using one of the commands below:

pip3 install buildozer
//
pip install buildozer

Now, we’ll install some of Buildozer’s required dependencies. I am on Linux Ergo, so I’ll use Linux-specific commands. You should execute these commands one by one:

sudo apt update
sudo apt install -y git zip unzip openjdk-13-jdk python3-pip autoconf libtool pkg-config zlib1g-dev libncurses5-dev libncursesw5-dev libtinfo5 cmake libffi-dev libssl-dev

pip3 install --upgrade Cython==0.29.19 virtualenv 

# add the following line at the end of your ~/.bashrc file
export PATH=$PATH:~/.local/bin/

After executing the specific commands, run buildozer init. You should see an output similar to the screenshot below:

Buildozer successful initialization

The command above creates a Buildozer .spec file, which you can use to make specifications to your app, including the name of the app, the icon, etc. The .spec file should look like the code block below:

[app]

# (str) Title of your application
title = My Application

# (str) Package name
package.name = myapp

# (str) Package domain (needed for android/ios packaging)
package.domain = org.test

# (str) Source code where the main.py live
source.dir = .

# (list) Source files to include (let empty to include all the files)
source.include_exts = py,png,jpg,kv,atlas

# (list) List of inclusions using pattern matching
#source.include_patterns = assets/*,images/*.png

# (list) Source files to exclude (let empty to not exclude anything)
#source.exclude_exts = spec

# (list) List of directory to exclude (let empty to not exclude anything)
#source.exclude_dirs = tests, bin

# (list) List of exclusions using pattern matching
#source.exclude_patterns = license,images/*/*.jpg

# (str) Application versioning (method 1)
version = 0.1

# (str) Application versioning (method 2)
# version.regex = __version__ = \['"\](.*)['"]
# version.filename = %(source.dir)s/main.py

# (list) Application requirements
# comma separated e.g. requirements = sqlite3,kivy
requirements = python3,kivy

# (str) Custom source folders for requirements
# Sets custom source for any requirements with recipes
# requirements.source.kivy = ../../kivy

# (list) Garden requirements
#garden_requirements =

# (str) Presplash of the application
#presplash.filename = %(source.dir)s/data/presplash.png

# (str) Icon of the application
#icon.filename = %(source.dir)s/data/icon.png

# (str) Supported orientation (one of landscape, sensorLandscape, portrait or all)
orientation = portrait

# (list) List of service to declare
#services = NAME:ENTRYPOINT_TO_PY,NAME2:ENTRYPOINT2_TO_PY

#
# OSX Specific
#

#
# author = © Copyright Info

# change the major version of python used by the app
osx.python_version = 3

# Kivy version to use
osx.kivy_version = 1.9.1

#
# Android specific
#

# (bool) Indicate if the application should be fullscreen or not
fullscreen = 0

# (string) Presplash background color (for new android toolchain)
# Supported formats are: #RRGGBB #AARRGGBB or one of the following names:
# red, blue, green, black, white, gray, cyan, magenta, yellow, lightgray,
# darkgray, grey, lightgrey, darkgrey, aqua, fuchsia, lime, maroon, navy,
# olive, purple, silver, teal.
#android.presplash_color = #FFFFFF

# (list) Permissions
#android.permissions = INTERNET

# (int) Target Android API, should be as high as possible.
#android.api = 27

# (int) Minimum API your APK will support.
#android.minapi = 21

# (int) Android SDK version to use
#android.sdk = 20

# (str) Android NDK version to use
#android.ndk = 19b

# (int) Android NDK API to use. This is the minimum API your app will support, it should usually match android.minapi.
#android.ndk_api = 21

# (bool) Use --private data storage (True) or --dir public storage (False)
#android.private_storage = True

# (str) Android NDK directory (if empty, it will be automatically downloaded.)
#android.ndk_path =

# (str) Android SDK directory (if empty, it will be automatically downloaded.)
#android.sdk_path =

# (str) ANT directory (if empty, it will be automatically downloaded.)
#android.ant_path =

# (bool) If True, then skip trying to update the Android sdk
# This can be useful to avoid excess Internet downloads or save time
# when an update is due and you just want to test/build your package
# android.skip_update = False

# (bool) If True, then automatically accept SDK license
# agreements. This is intended for automation only. If set to False,
# the default, you will be shown the license when first running
# buildozer.
# android.accept_sdk_license = False

# (str) Android entry point, default is ok for Kivy-based app
#android.entrypoint = org.renpy.android.PythonActivity

# (str) Android app theme, default is ok for Kivy-based app
# android.apptheme = "@android:style/Theme.NoTitleBar"

# (list) Pattern to whitelist for the whole project
#android.whitelist =

# (str) Path to a custom whitelist file
#android.whitelist_src =

# (str) Path to a custom blacklist file
#android.blacklist_src =

# (list) List of Java .jar files to add to the libs so that pyjnius can access
# their classes. Don't add jars that you do not need, since extra jars can slow
# down the build process. Allows wildcards matching, for example:
# OUYA-ODK/libs/*.jar
#android.add_jars = foo.jar,bar.jar,path/to/more/*.jar

# (list) List of Java files to add to the android project (can be java or a
# directory containing the files)
#android.add_src =

# (list) Android AAR archives to add (currently works only with sdl2_gradle
# bootstrap)
#android.add_aars =

# (list) Gradle dependencies to add (currently works only with sdl2_gradle
# bootstrap)
#android.gradle_dependencies =

# (list) add java compile options
# this can for example be necessary when importing certain java libraries using the 'android.gradle_dependencies' option
# see https://developer.android.com/studio/write/java8-support for further information
# android.add_compile_options = "sourceCompatibility = 1.8", "targetCompatibility = 1.8"

# (list) Gradle repositories to add {can be necessary for some android.gradle_dependencies}
# please enclose in double quotes 
# e.g. android.gradle_repositories = "maven { url 'https://kotlin.bintray.com/ktor' }"
#android.add_gradle_repositories =

# (list) packaging options to add 
# see https://google.github.io/android-gradle-dsl/current/com.android.build.gradle.internal.dsl.PackagingOptions.html
# can be necessary to solve conflicts in gradle_dependencies
# please enclose in double quotes 
# e.g. android.add_packaging_options = "exclude 'META-INF/common.kotlin_module'", "exclude 'META-INF/*.kotlin_module'"
#android.add_gradle_repositories =

# (list) Java classes to add as activities to the manifest.
#android.add_activities = com.example.ExampleActivity

# (str) OUYA Console category. Should be one of GAME or APP
# If you leave this blank, OUYA support will not be enabled
#android.ouya.category = GAME

# (str) Filename of OUYA Console icon. It must be a 732x412 png image.
#android.ouya.icon.filename = %(source.dir)s/data/ouya_icon.png

# (str) XML file to include as an intent filters in <activity> tag
#android.manifest.intent_filters =

# (str) launchMode to set for the main activity
#android.manifest.launch_mode = standard

# (list) Android additional libraries to copy into libs/armeabi
#android.add_libs_armeabi = libs/android/*.so
#android.add_libs_armeabi_v7a = libs/android-v7/*.so
#android.add_libs_arm64_v8a = libs/android-v8/*.so
#android.add_libs_x86 = libs/android-x86/*.so
#android.add_libs_mips = libs/android-mips/*.so

# (bool) Indicate whether the screen should stay on
# Don't forget to add the WAKE_LOCK permission if you set this to True
#android.wakelock = False

# (list) Android application meta-data to set (key=value format)
#android.meta_data =

# (list) Android library project to add (will be added in the
# project.properties automatically.)
#android.library_references =

# (list) Android shared libraries which will be added to AndroidManifest.xml using <uses-library> tag
#android.uses_library =

# (str) Android logcat filters to use
#android.logcat_filters = *:S python:D

# (bool) Copy library instead of making a libpymodules.so
#android.copy_libs = 1

# (str) The Android arch to build for, choices: armeabi-v7a, arm64-v8a, x86, x86_64
android.arch = armeabi-v7a

# (int) overrides automatic versionCode computation (used in build.gradle)
# this is not the same as app version and should only be edited if you know what you're doing
# android.numeric_version = 1

#
# Python for android (p4a) specific
#

# (str) python-for-android fork to use, defaults to upstream (kivy)
#p4a.fork = kivy

# (str) python-for-android branch to use, defaults to master
#p4a.branch = master

# (str) python-for-android git clone directory (if empty, it will be automatically cloned from github)
#p4a.source_dir =

# (str) The directory in which python-for-android should look for your own build recipes (if any)
#p4a.local_recipes =

# (str) Filename to the hook for p4a
#p4a.hook =

# (str) Bootstrap to use for android builds
# p4a.bootstrap = sdl2

# (int) port number to specify an explicit --port= p4a argument (eg for bootstrap flask)
#p4a.port =


#
# iOS specific
#

# (str) Path to a custom kivy-ios folder
#ios.kivy_ios_dir = ../kivy-ios
# Alternately, specify the URL and branch of a git checkout:
ios.kivy_ios_url = https://github.com/kivy/kivy-ios
ios.kivy_ios_branch = master

# Another platform dependency: ios-deploy
# Uncomment to use a custom checkout
#ios.ios_deploy_dir = ../ios_deploy
# Or specify URL and branch
ios.ios_deploy_url = https://github.com/phonegap/ios-deploy
ios.ios_deploy_branch = 1.7.0

# (str) Name of the certificate to use for signing the debug version
# Get a list of available identities: buildozer ios list_identities
#ios.codesign.debug = "iPhone Developer: <lastname> <firstname> (<hexstring>)"

# (str) Name of the certificate to use for signing the release version
#ios.codesign.release = %(ios.codesign.debug)s


[buildozer]

# (int) Log level (0 = error only, 1 = info, 2 = debug (with command output))
log_level = 2

# (int) Display warning if buildozer is run as root (0 = False, 1 = True)
warn_on_root = 1

# (str) Path to build artifact storage, absolute or relative to spec file
# build_dir = ./.buildozer

# (str) Path to build output (i.e. .apk, .ipa) storage
# bin_dir = ./bin

#    -----------------------------------------------------------------------------
#    List as sections
#
#    You can define all the "list" as [section:key].
#    Each line will be considered as a option to the list.
#    Let's take [app] / source.exclude_patterns.
#    Instead of doing:
#
#[app]
#source.exclude_patterns = license,data/audio/*.wav,data/images/original/*
#
#    This can be translated into:
#
#[app:source.exclude_patterns]
#license
#data/audio/*.wav
#data/images/original/*
#


#    -----------------------------------------------------------------------------
#    Profiles
#
#    You can extend section / key with a profile
#    For example, you want to deploy a demo version of your application without
#    HD content. You could first change the title to add "(demo)" in the name
#    and extend the excluded directories to remove the HD content.
#
#[app@demo]
#title = My Application (demo)
#
#[app:source.exclude_patterns@demo]
#images/hd/*
#
#    Then, invoke the command line with the "demo" profile:
#
#buildozer --profile demo android debug

If you want to specify things like the icon, requirements, loading screen, etc., you should edit this file. After making all the desired edits to your application, run buildozer -v android debug from your app directory to build and compile your application. This may take a while, especially if you have a slow machine.

After the process is done, your terminal should have some logs, one confirming that the build was successful:

Android successful build

You should also have an APK version of your app in your bin directory. This is the application executable that you will install and run on your phone:

Android .apk in the bin directory

Conclusion

Congratulations! If you have followed this tutorial step by step, you should have a simple random number generator app on your phone. Play around with it and tweak some values, then rebuild. Running the rebuild will not take as much time as the first build.

As you can see, building a mobile application with Python is fairly straightforward, as long as you are familiar with the framework or module you are working with. Regardless, the logic is executed the same way.

Get familiar with the Kivy module and it’s widgets. You can never know everything all at once. You only need to find a project and get your feet wet as early as possible. Happy coding.

Link: https://blog.logrocket.com/build-android-application-kivy-python-framework/

#python