使用github API的简单github个人资料页面显示应用程序,以及使用bloc模式的flutter...

    科技2022-08-01  101

    In this article, you will learn about state management using BloC in Flutter along with fetching API, Futures in Flutter and basic Widgets.

    在本文中,您将了解在Flutter中使用BloC以及获取API,Flutter中的Futures和基本Widget进行状态管理的知识。

    Code: https://github.com/suparthghimire/github_profile

    代码: https : //github.com/suparthghimire/github_profile

    BLoC pattern in Flutter has become a standard way to manage states in an application ever since Google announced the support of BLoC in Google I/O 2018. Although basic understanding of using BLoC in Flutter is easy, actual implementation of it might be tricky. Today through this article, you will be able to build an application using Flutter that features BLoC as its State Management pattern.

    自从Google在Google I / O 2018中宣布对BLoC的支持以来,Flutter中的BLoC模式已成为管理应用程序中状态的标准方法。尽管对在Flutter中使用BloC的基本理解很容易,但实际实施起来可能很棘手。 今天,通过本文,您将能够使用Flutter构建一个将BLoC作为其状态管理模式的应用程序。

    We will first start by understanding the application we will be creating. Here is a simple Demo of what we will be building today:

    首先,我们将从了解将要创建的应用程序开始。 这是我们今天将要构建的简单演示:

    As in the GIF aside, the app features following states:

    与GIF一样,该应用程序还具有以下状态:

    a) Initial State: Has only Text Input Field

    a)初始状态:只有文本输入字段

    b) Loading State: After pressing search button on keyboard

    b)加载状态:按键盘上的搜索按钮后

    c) Loaded State: Data is fetched from API and displayed on screen

    c)加载状态:从API提取数据并显示在屏幕上

    d) Error State: Error message is displayed when no user exists.

    d)错误状态:当不存在用户时显示错误消息。

    So lets create a fresh project in our root directory by typing following command in out CLI:

    因此,通过在CLI中键入以下命令,在根目录中创建一个新项目:

    GitBash CLI to generate new Flutter Project GitBash CLI生成新的Flutter项目

    I am using Git Bash. You can use any CLI that you prefer.

    我正在使用Git Bash。 您可以使用任何喜欢的CLI。

    After the project is created, open it in your preferred text editor (I will be using VS Code).

    创建项目后,在首选的文本编辑器中打开它(我将使用VS Code)。

    Before moving on to UI of the application. we will work on all the operations that happens under the hood, and at the end, we will build the UI of the application.

    在转到应用程序的UI之前。 我们将处理所有在幕后进行的操作,最后,我们将构建应用程序的UI。

    Before starting the actual coding. we will be in need of some dependencies for our application that will help us create boiler plate for BLoC state management, and also to fetch the API from the web.

    在开始实际编码之前。 我们需要为我们的应用程序提供一些依赖关系,这将有助于我们为BLoC状态管理创建样板,并从网络中获取API。

    In the pubspec.yaml file of the root of your project, we will be adding these two packages: flutter_bloc and http.

    在项目根目录的pubspec.yaml文件中,我们将添加以下两个软件包:flutter_bloc和http。

    As the name suggests, flutter_bloc is the package that will generate much of the boiler plate code for BLoC in our application and http package is responsible to handle get requests and responses from out raw data repository or API.

    顾名思义,flutter_bloc是一个软件包,它将为我们的应用程序中的BLoC生成许多样板代码,而http软件包则负责处理来自原始数据存储库或API的获取请求和响应。

    Updated pubspec.yaml file 更新了pubspec.yaml文件

    Add the dependencies as in the Image above. Note that the indents in the file matters. The versions of these dependencies need not to be provided as “flutter pub get” automatically grabs the latest package available if versions are not provided.

    如上图所示添加依赖项。 请注意, 文件中的缩进很重要 。 这些依赖项的版本不需要提供,因为如果未提供版本,“ flutter pub get”会自动获取可用的最新软件包。

    (Note: For More information on these packages, here is the link to their respective pub pages flutter_bloc: https://pub.dev/packages/flutter_bloc http: https://pub.dev/packages/http )

    (注意:有关这些软件包的更多信息,请参见以下链接到各自的发布页面flutter_bloc: https : //pub.dev/packages/flutter_bloc http: https : //pub.dev/packages/ http)

    Now, if you are working in VS Code as I am, you can install some extensions that will tremendously help you while you develop applications in flutter. They are:

    现在,如果您按原样使用VS Code,则可以安装一些扩展,这些扩展将在您快速开发应用程序时极大地帮助您。 他们是:

    a) AutoImport

    a)自动导入

    b) Awesome Flutter Snippets

    b)很棒的Flutter片段

    c) bloc

    c)团体

    d) Bracket Colorizer 2

    d)支架着色器2

    e) Dart

    e)Dart

    f) Flutter

    f)颤振

    Now after all the setup is done, lets first run our application in the emulator that is installed in your computer or even in your phone via USB cable. If any error is shown in the debug console of your application, it is very likely that you might have misplaced the dependency code in your pubspec.yaml file. If the error persists after you fix any errors in pubspec.yaml file, then you might have to reinstall flutter and dart in your computer.

    现在,完成所有设置后,让我们首先通过USB电缆在计算机甚至手机中安装的仿真器中运行我们的应用程序。 如果应用程序的调试控制台中显示任何错误,则很可能是您已将依赖项代码放到pubspec.yaml文件中。 如果修复pubspec.yaml文件中的任何错误后错误仍然存​​在,则可能必须在计算机中重新安装flutter和dart。

    Now lets jump into making the application. As I stated above, we will start by first creating the backbone of our application. Hence, first lets manage all the folders that we will be needing while creating the application. Inside the lib folder, lets create two folders pages and data.

    现在开始制作应用程序。 如上所述,我们将首先创建应用程序的主干。 因此,首先让我们管理在创建应用程序时需要的所有文件夹。 在lib文件夹中,让我们创建两个文件夹页面和数据。

    The pages folder will contain all our UI and the data folder will contain the code for out API call and the model for converting the incoming JSON data into desired Dart object

    pages文件夹将包含我们所有的UI,data文件夹将包含用于API调用的代码以及用于将传入JSON数据转换为所需Dart对象的模型

    Updated File Structure inside lib folder lib文件夹中的更新文件结构

    Now inside the data folder, we will have a file named “DataReporitory.dart”. This file will contain the class for the method that will be used to fetch data from the GitHub api. Also, data folder will contain another folder model and inside the model folder, we will store “Profile.dart” file that will contain a Profile class which will be used to determine the type of data that we will parse form incoming JSON response from API.

    现在在数据文件夹中,我们将有一个名为“ DataReporitory.dart”的文件。 该文件将包含用于从GitHub API获取数据的方法的类。 另外,数据文件夹将包含另一个文件夹模型,并且在模型文件夹中,我们将存储“ Profile.dart”文件,该文件将包含一个Profile类,该类将用于确定我们将从API传入的JSON响应中解析的数据类型。

    Updated Folder Structure inside lib folder 更新lib文件夹内的文件夹结构

    So first lets create our Profile model. Inside the Profile.dart file, create a class Profile with final variables name, followers, following, image, repos and bio.

    因此,首先让我们创建我们的Profile模型。 在Profile.dart文件中,使用最终变量名称,关注者,关注者,图像,存储库和生物变量创建类Profile。

    Profile Model 轮廓模型

    The Profile class as shown, has final variables that will be used later to parse JSON data into Dart object, which can be then used in out UI to display information of users.

    如图所示,Profile类具有最终变量,这些变量将在以后用于将JSON数据解析为Dart对象,然后可以在out UI中使用它来显示用户信息。

    Now that the Profile model is complete, lets move on to creating the class fetching data from API. Inside DataRepository.dart file, write the following code:

    现在,Profile模型已经完成,让我们继续创建从API获取数据的类。 在DataRepository.dart文件中,编写以下代码:

    DataRepository Class and FetchUser method DataRepository类和FetchUser方法

    Here in the code, we see that the class DataRepository has a function fetchUser that accepts a String (userName) and returns a Future in the form of the Profile model that we just made.

    在代码中,我们看到类DataRepository具有一个函数fetchUser,该函数接受一个String(userName)并以我们刚刚创建的Profile模型的形式返回一个Future。

    The function contains the API that is used to fetch the user that we search from the application.

    该函数包含用于获取我们从应用程序中搜索的用户的API。

    (Note: You can view your profile in JSON format in the browser by going to https://api.github.com/users/<your_user_name>, replacing <your_user_name> with your username in GitHub).

    (注意:您可以转到 https://api.github.com/users/ <your_user_name> ,用GitHub中的用户名替换<your_user_name>,从而 在浏览器中以JSON格式查看个人资料 )。

    Now, the function returns a get request that accepts the API. As fetching the API is asynchronous in nature, we need to utilize await keyword before the get(api) method. Also, using await keyword requires the function to be asynchronous, thus async keyword is used after fetchUser(String userName).

    现在,该函数返回一个接受API的get请求。 由于获取API本质上是异步的,因此我们需要在get(api)方法之前使用await关键字。 另外,使用await关键字要求函数异步,因此在fetchUser(String userName)之后使用async关键字。

    Now, just as in JavaScript Promises, if the get request resolves the promise, then we need to perfoem some action. Hence, inside the .then method which accepts the data from the response after get request is sent, we parse the JSON data in Dart object using json.decode(), by passing the data received from response of get request.

    现在,就像在JavaScript Promises中一样,如果get请求解决了Promise,那么我们需要执行一些操作。 因此,在发送get请求后从响应中接受数据的.then方法内部,我们通过传递从get请求的响应中接收到的数据,使用json.decode()解析Dart对象中的JSON数据。

    Now, we need to check if the provided username is valid or not. For it, first lets try to find what happens if some gibberish is passed to the GitHub API.

    现在,我们需要检查提供的用户名是否有效。 为此,首先让我们尝试查找将一些乱码传递给GitHub API会发生的情况。

    Go to this link here: https://api.github.com/users/fjdsklfjdslfdslk

    在此处转到此链接: https : //api.github.com/users/fjdsklfjdslfdslk

    Invalid User Response From API 来自API的无效用户响应

    As we see, the response we get is still in JSON. However, the JSON contains a key “message” with value “Not Found” instead key and values that we would receive on a valid username. Hence, we need to throw a UserNotFoundException() when the JSON response has a key “message” and the value of message is not null.

    如我们所见,我们得到的响应仍然是JSON。 但是,JSON包含键“ message”,其值为“ Not Found”,而不是键和我们将在有效用户名上收到的值。 因此,当JSON响应具有键“ message”并且message的值不为null时,我们需要抛出UserNotFoundException()。

    If the JSON response has no key “message” then we need to parse the JSON data into out Profile Model and return the Profile model from this function as fetchUser function returns a Future of type Profile. Thus, a final variable profile is instantiated to the Profile class by passing the required parameters that are now been decoded into jsonData variable.

    如果JSON响应没有关键的“消息”,那么我们需要将JSON数据解析为Profile模型,并从此函数返回Profile模型,因为fetchUser函数返回的是Future类型的Profile。 因此,通过将现在已解码的必需参数传递到jsonData变量中,将最终变量配置文件实例化到Profile类。

    Finally then, the profile variable is returned from the function.

    最后,从函数返回配置文件变量。

    Now, if due to some reasons, we could not complete the request to API, then we again need to catch it. Thus the catchError method is used that accepts the context. Here, we again throw the UserNotFoundException().

    现在,如果由于某些原因,我们无法完成对API的请求,那么我们再次需要捕获它。 因此,使用catchError方法接受上下文。 在这里,我们再次抛出UserNotFoundException()。

    Finally, UserNotFoundException() is not a valid class in Dart. Hence, we need to create the class ourselves. And since this is a Exception, we can simply inherit the Exception class that is a valid class in Dart, which is used to handle any sort of Exception.

    最后,UserNotFoundException()在Dart中不是有效的类。 因此,我们需要自己创建类。 由于这是一个Exception,因此我们可以简单地继承Exception类,它是Dart中的有效类,用于处理任何类型的Exception。

    This finally concludes out data part of the application. Now the only thing left to do is tie up these data to the UI. For these, now we require the BLoC pattern. We will listen to the submit event that is provided by the user, and we will return a state based on the event provided.

    最终得出了应用程序的数据部分。 现在剩下要做的就是将这些数据绑定到UI。 对于这些,现在我们需要BLoC模式。 我们将侦听用户提供的Submit事件,并根据提供的事件返回状态。

    So before creating the UI, lets create BLoC files that are required.

    因此,在创建UI之前,让我们创建所需的BLoC文件。

    Fortunately, the basic boiler plate code can be automatically generated in VS Code. Right Click on the lib folder of your application and click on BLoC: new BLoC option. A bar will appear on our screen that will ask for the name that we need to provide to create our abstract classes for BLoC, state and events.

    幸运的是,基本的锅炉板代码可以在VS Code中自动生成。 右键单击应用程序的lib文件夹,然后单击BLoC:新的BLoC选项。 屏幕上将出现一个栏,要求您提供名称以创建BLoC,状态和事件的抽象类。

    Creating Boiler-Plate code for BLoC 为BLoC创建锅炉板代码

    Now, the bloc folder inside lib folder must have 3 files: profile_bloc.dart, profile_event.dart and profile_state.dart.

    现在,lib文件夹中的bloc文件夹必须具有3个文件:profile_bloc.dart,profile_event.dart和profile_state.dart。

    Now lets discuss about the the three generated files, before jumping into coding in them. We know in BLoC, event is transferred into State. This conversion is done inside the profile_bloc.dart file. The profile_event.dart file contains the events that can come in the Stream and the profile_state.dart file contains all the States of our application.

    现在让我们讨论三个生成的文件,然后再进行编码。 我们知道在BLoC中,事件已转移到州。 此转换在profile_bloc.dart文件中完成。 profile_event.dart文件包含流中可能出现的事件,而profile_state.dart文件包含应用程序的所有状态。

    As event is the input to the Stream, lets look at the code that we have to place inside profile_event.dart file.

    由于事件是流的输入,因此让我们看一下我们必须放在profile_event.dart文件中的代码。

    This is the Boiler Plate code generated by the BLoC: new BLoC option in out text editor.

    这是BLoC生成的样板代码:out文本编辑器中的新BLoC选项。

    Boiler Plate of ProfileEvent Class ProfileEvent类的锅炉板

    Here, the first line suggests that this code is a part of profile_bloc.dart file (we will look at it in a while).

    在这里,第一行表明此代码是profile_bloc.dart文件的一部分(我们将在一段时间内对其进行查看)。

    The next line has an annotation @immutable which means that the class below is final and its state cannot change. Which is true because the ProfileEvent class has to be immutable and abstract as other events has to inherit from it.

    下一行有一个@不可变注解,表示下面的类是final,其状态不能更改。 这是正确的,因为ProfileEvent类必须是不可变的且抽象的,因为其他事件必须从该类继承。

    The code that we add to this file is here:

    我们添加到该文件中的代码在这里:

    Modified ProfileEvent Class 修改的ProfileEvent类

    Here, we created a class GetUser that inherits or extends from ProfileEvent class. This class can now be aliased as an event for the application (GetUser event). Inside this event, we just declare a String userName and pass it inside the constructor.

    在这里,我们创建了一个GetUser类,该类继承或继承了ProfileEvent类。 现在,可以将此类作为应用程序的事件(GetUser事件)的别名。 在此事件内部,我们只声明一个字符串userName并将其传递到构造函数中。

    Confused? Don’t worry! The three files are meant to be understood together. Now lets see what is inside the profile_state.dart file.

    困惑? 不用担心 这三个文件旨在一起理解。 现在让我们查看profile_state.dart文件中的内容。

    The Boiler Plate code generated bu BLoC: new BLoC is

    BLoC生成的锅炉板代码:

    Boiler Plate of ProfileState Class ProfileState类的锅炉板

    Here too the first line suggests that this file is a part of profile_bloc.dart file. Here, we see that similar to the ProfileEvent class, the ProfileState class is immutable and is abstract. This is also true, because the base class for other states must be final and its state cannot change as other classes will be inheriting from it.

    这里的第一行也表明此文件是profile_bloc.dart文件的一部分。 在这里,我们看到类似于ProfileEvent类,ProfileState类是不可变的并且是抽象的。 这也是正确的,因为其他状态的基类必须是final,并且其状态不能更改,因为其他类将从该基类继承。

    The changes what we do to this class are:

    我们对此类所做的更改是:

    Modified ProfileEvent Class 修改的ProfileEvent类

    Here, first we create all the state classes inheriting the ProfileState class that our application may have. Here, in this instance, out application will have an initial state where no data is shown, then there is the loading state where data is being fetched via the API, loaded state where data has successfully been loaded from the API and is shown in the UI of the application and finally there is the error state which is responsible to manage error state of the application.

    在这里,首先我们创建所有继承我们应用程序可能具有的ProfileState类的状态类。 在此情况下,在这种情况下,输出应用程序将具有一个初始状态,其中未显示任何数据,然后是一种加载状态,其中正在通过API提取数据,另一种加载状态是已成功从API加载数据,并显示在应用程序的用户界面,最后是错误状态,该错误状态负责管理应用程序的错误状态。

    You may notice, each of these classes has their constructors with a const keyword before them. These constant constructors in dart created “Canonicalized” instances. This means that the object created from these classes have their field values known at the compile time. This improves the app performance by a huge margin and also is a good programming practice in dart.

    您可能会注意到,这些类中的每一个都有一个带有const关键字的构造函数。 dart中的这些常量构造函数创建了“规范化”实例。 这意味着从这些类创建的对象在编译时就知道其字段值。 这极大地提高了应用程序的性能,也是Dart的良好编程习惯。

    Now, in the ProfileLoaded class we require certain data to be loaded. The data needs to come from the Profile model that we created earlier as through this ProfileLoaded state, we will display the data to out ui. Thus, an object of Profile class is created in the ProfileLoaded class and it is passed into the constructor.

    现在,在ProfileLoaded类中,我们需要加载某些数据。 数据需要来自我们之前通过ProfileLoaded状态创建的Profile模型,我们将数据显示给ui。 因此,在ProfileLoaded类中创建Profile类的对象,并将其传递到构造函数中。

    Finally in the ProfileError field, we need to find out what error is thrown. Although our application has only one exception (UserNotFound), there might be some instances in your application where multiple exceptions might occur. Thus, we pass a variable of type String through it.

    最后,在ProfileError字段中,我们需要找出引发了什么错误。 尽管我们的应用程序只有一个异常(UserNotFound),但是在您的应用程序中可能会有一些实例发生多个异常。 因此,我们通过它传递了String类型的变量。

    Still Confused? Don’t worry! Now everything will fall into place once we talk about the profile_bloc.dart file.

    还是很困惑? 不用担心 现在,一旦我们谈论profile_bloc.dart文件,一切都会准备就绪。

    The boiler plate code is as follows:

    样板代码如下:

    Boiler Plate code of ProfileBloc class ProfileBloc类的锅炉板代码

    This file profile_bloc.dart contains one class ProfileBloc that is responsible for all the conversion that is required for any event passed to produce a state.

    此文件profile_bloc.dart包含一个ProfileBloc类,该类负责负责传递任何产生状态的事件所需的所有转换。

    Let’s look at the code from top (excluding the imports)

    让我们从顶部看代码(不包括导入)

    Here we see that there are two lines containing the “part ”keyword and it suggests that the files profile_event.dart and profile_state.dart are the part of this file.

    在这里,我们看到有两行包含“ part ”关键字,这表明文件profile_event.dart和profile_state.dart是该文件的一部分。

    Now, here we have a class ProfileBloc that extends Bloc class that is of generic type accepting Profile Event and Profile State. What does that mean?

    现在,这里有一个ProfileBloc类,该类扩展了Bloc类,该类具有接受Profile Event和Profile State的通用类型。 那是什么意思?

    We all know that a BLoC is the pattern that has to get an event to pass out State from the stream. This simple conversion using StreamControllers, Stream and Sink is done under the hood by the BLoC class that has been inherited. However, it requires the Instances of the event and State for it to make sure that the event that is passed is properly converted into a State. Hence we pass these two instances to the BLoC.

    我们都知道,BLoC是必须获取事件以从流中传递State的模式。 使用StreamControllers,Stream和Sink进行的这种简单转换是通过已继承的BLoC类在后台完成的。 但是,它需要事件实例和状态实例,以确保将传递的事件正确转换为状态。 因此,我们将这两个实例传递给BLoC。

    The class ProfileBloc also has a constructor ProfileBloc() what has a super constructor that accepts the ProfileInitial() state from profile_state.dart file. This ultimately means that the initial state of the application is ProfileInitial() in spite of passing any data inside the ProfileBloc constructor. After initial state however, other states can definitely make use of the data passed in the ProfileBloc constructor.

    ProfileBloc类还具有一个构造函数ProfileBloc(),该构造函数具有一个超级构造函数,该构造函数接受profile_state.dart文件中的ProfileInitial()状态。 这最终意味着,尽管在ProfileBloc构造函数中传递了任何数据,但应用程序的初始状态是ProfileInitial()。 但是,在初始状态之后,其他状态肯定可以使用在ProfileBloc构造函数中传递的数据。

    Below it, we have a method mapEventToState that returns Stream in the form of ProfileState by accepting an Event from Profile Event. Now before moving ahead, lets talk in brief about Streams in dart. But so you know, I will not be going in detail about Streams in this article.

    在它下面,我们有一个方法mapEventToState,它通过接受Profile Event中的事件以ProfileState的形式返回Stream。 现在,在继续前进之前,让我们简要介绍一下dart中的Streams。 但是,您知道,在本文中我不会详细介绍Streams。

    Streams are like a pipe that consists of two endpoints from where data can flow. This is similar to Futures, however, Futures in Dart only send single data at one time. Streams however can pass multiple data simultaneously which makes them ideal for state management as multiple events can be passed to the stream and respective states can be accessed.

    流就像一个管道,由两个端点组成,数据可以从该端点流向该端点。 这类似于期货,但是Dart中的期货一次只能发送单个数据。 但是,流可以同时传递多个数据,这使它们成为状态管理的理想选择,因为可以将多个事件传递给流,并且可以访问相应的状态。

    Now you may be confused seeing the “*” symbol aside async keyword which normally isnt there in Futures. Well, as Futures only deal with single data, async keyword is used. But Streams deal with multiple data simultaneously. Thus to represent the stream of asynchronous data, we denote it by an asterisk symbol.

    现在您可能会感到困惑,因为async关键字旁通常没有在期货关键字中出现“ *”符号。 好吧,由于期货只处理单个数据,因此使用了async关键字。 但是Streams可同时处理多个数据。 因此,为了表示异步数据流,我们用星号将其表示。

    Now that the generated code is clear, we need to define how will state be changing in our application. The state will change when data is being fetched to the user. As the initial state of the app has already been declared by super constructor, we only need to deal with remaining 3 states of the app.

    现在,生成的代码已经清晰了,我们需要定义状态在应用程序中的变化方式。 当数据被提取给用户时,状态将改变。 由于应用程序的初始状态已经由超级构造函数声明,因此我们只需要处理应用程序的其余3种状态。

    Lets look at the code that we need to write for it

    让我们看一下我们需要为其编写的代码

    Boiler Plate code of ProfileBloc class ProfileBloc类的锅炉板代码

    So from the code, it looks as if we added very few lines. But these lines carries a lot of significance in the application.

    因此,从代码来看,好像我们增加了很少的几行。 但是这些行在应用中具有很多意义。

    Before out ProfileBloc constructor, we see that an object of DataRepository class is created. This is important as we have to access the function fetchUser that was creared in DataRepository class which fetched user from API and also parsed it into Dart object.

    在使用ProfileBloc构造函数之前,我们看到已创建DataRepository类的对象。 这很重要,因为我们必须访问DataRepository类中预先定义的函数fetchUser,该函数从API提取用户并将其解析为Dart对象。

    In the method, we see that try catch block is used, which is standard to catch any exceptions that might be generated, which we know will be the case if an invalid user is passed to the API.

    在该方法中,我们看到使用了try catch块,这是捕获可能生成的任何异常的标准方法,我们知道,如果将无效用户传递给API,情况就会如此。

    Now, we see that there is an if statement that checks if the event is GetUser event. Remember we made an Event in out ProfileEvent class in profile_event.dart file? That event is now being used in this if statement to check whether the event that is passed as parameter to the mapEventToState method is a GetUser event. This seems like an odd comparison to do as our app has no other events. Which is true, but consider a case where you might want to have another event (Add User, for example) in the same scope. Then you might not want the app to display states in the same way as it will when the event is a GetUser event.

    现在,我们看到有一个if语句,用于检查事件是否为GetUser事件。 还记得我们在profile_event.dart文件的ProfileEvent类中创建了一个Event吗? 现在,在此if语句中使用该事件来检查作为参数传递给mapEventToState方法的事件是否为GetUser事件。 这似乎有点奇怪,因为我们的应用没有其他事件。 的确如此,但是考虑一种情况,您可能希望在同一作用域中拥有另一个事件(例如,添加用户)。 然后,您可能不希望该应用程序以与事件为GetUser事件时的状态相同的方式显示状态。

    Now if the event is GetUser event, then the first state that must get fired up is the ProfileLoading state.When user enters a username in the UI then it will take some time to fetch that user and display it to the screen. Thus, we need to display a loading screen while the app fetches a user. Now to send a state from an event, we use the keyword yield in dart.

    现在如果事件是GetUser事件,则必须触发的第一个状态是ProfileLoading状态。当用户在UI中输入用户名时,将需要一些时间来获取该用户并将其显示在屏幕上。 因此,我们需要在应用程序获取用户时显示加载屏幕。 现在要发送事件的状态,我们在dart中使用关键字yield。

    Now while the ProfileLoading state is being yielded, we also need to grab the user from API. Hence, we need a profile to grab a user’s profile from DataRepository object that we created earlier. So a final variable profile is created that fetches a user using the DataRepository object that was created and the method fetchUser() is called. The fetchUser however, asks for a String of username. This string is passed via the event as in our ProfileEvent class, when we created the GetUser event, we created a String username and passed it in the constructor.

    现在,当产生ProfileLoading状态时,我们还需要从API抓取用户。 因此,我们需要一个配置文件来从我们之前创建的DataRepository对象中获取用户的配置文件。 因此,创建了一个最终的变量概要文件,该概要文件使用创建的DataRepository对象获取用户并调用了方法fetchUser()。 但是,fetchUser要求输入用户名字符串。 该字符串通过事件传递,就像在ProfileEvent类中一样,当我们创建GetUser事件时,我们创建了一个String用户名并将其传递给构造函数。

    Finally, after the profile is fetched, then another state ProfileLoaded needs to be yielded. Now we know from ProfileState class in profile_state.dart file, we made a ProfileLoaded state that accepts a particular Profile of a user so that it could be used to display in the UI of the app. Hence, while yielding the ProfileLoaded class, we also pass the profile variable that has the information about the profile of the user.

    最后,在提取概要文件之后,需要产生另一个状态ProfileLoaded。 现在我们从profile_state.dart文件中的ProfileState类知道,我们创建了一个ProfileLoaded状态,该状态接受用户的特定Profile,以便可以将其显示在应用程序的UI中。 因此,在生成ProfileLoaded类时,我们还传递了具有有关用户个人资料信息的个人资料变量。

    Now at the end, if none of the states have been yielded, then we know that an exception has been thrown from the DataRepository class. The only exception that can occur in our application is UserNotFoundException. Thus, after try block, we check for the exception. If UserNotFoundException is found, then we have to yield our final state, i.e ProfileError exception which accepts an error message. Now as we have defined a particular exception in the app, we can send a string of message inside the ProfileError class as its constructor accepts a string of Error message.

    现在最后,如果没有产生任何状态,那么我们知道已经从DataRepository类抛出了异常。 在我们的应用程序中可能发生的唯一异常是UserNotFoundException。 因此,在try块之后,我们检查异常。 如果找到UserNotFoundException,则必须产生最终状态,即ProfileError异常,它接受一条错误消息。 现在,我们已经在应用程序中定义了一个特殊的异常,我们可以在ProfileError类内发送一串消息,因为它的构造函数接受了一条Error消息。

    This concludes our logical and data section of the application. You might be surprised looking at the minimal lines of code that was needed to create the logic to fetch the API and manage states, but there definitely was alot to learn. Now lets quiclky make the UI and test out app!!!😊😊

    到此,我们结束了应用程序的逻辑和数据部分。 您可能会惊讶地发现创建逻辑以获取API和管理状态所需的最少代码行,但是肯定有很多东西需要学习。 现在让quiclky制作UI并测试应用程序!!!😊😊

    So lets look at all the possible widgets that our application needs. The very obvious one of course is the Text Field, then there is the Circular Loading Widget, the Screen to show details of the fetched user and finally if any exception occurs, we need to display a snack bar indicating the exception’s message. However, before constructing the UI of the application, lets talk about which widget to show on a particular state. We know that the BLoC yields 4 states to us, so based on them we display following widgets:

    因此,让我们看看我们的应用程序需要的所有可能的小部件。 当然,最明显的一个是“文本字段”,然后是“循环加载小部件”,它是一个屏幕,用于显示获取的用户的详细信息,最后,如果发生任何异常,我们需要显示一个小吃栏来指示异常的消息。 但是,在构造应用程序的UI之前,让我们先讨论在特定状态下显示哪个小部件。 我们知道BLoC向我们产生4个状态,因此基于它们,我们显示以下窗口小部件:

    a) ProfileInitial state is the initial state of the app. Thus, we only display the TextField widget.

    a)ProfileInitial状态是应用程序的初始状态。 因此,我们仅显示TextField小部件。

    b) ProfileLoading state is the state when the User data is being fetched from the API. Thus, we need to display a loading widget when ProfileLoading widget is yielded.

    b)ProfileLoading状态是从API提取用户数据时的状态。 因此,当产生ProfileLoading小部件时,我们需要显示一个加载小部件。

    c) ProfileLoaded state is the state when the user’s data is ready to be displayed, and hence we should show the page where user’s data is present and so is the TextField to input username of another user.

    c)ProfileLoaded状态是准备显示用户数据的状态,因此,我们应该显示存在用户数据的页面,TextField也应显示输入另一个用户的用户名。

    d) ProfileError state is yielded if any exception is caught by the app. Hence, we should indicate the user about the error by displaying a snack bar.

    d)如果应用程序捕获到任何异常,将产生ProfileError状态。 因此,我们应该通过显示小吃店来向用户指示错误。

    So, lets create the required folder structure for out Widgets to be build.

    因此,让我们为要构建的Widget创建所需的文件夹结构。

    Our application requires only one page, but 4 different widgets. So, inside pages folder, create a folder named widgets and also create a file names as GitProfile.dart.

    我们的应用程序仅需要一页,但是需要4个不同的小部件。 因此,在pages文件夹内,创建一个名为widgets的文件夹,并创建一个文件名为GitProfile.dart的文件。

    If we see in the emulator, we will have a default app that looks something like this:

    如果在模拟器中看到,我们将有一个默认应用程序,看起来像这样:

    Default App Generated by Flutter Flutter生成的默认应用

    This app is fun to play with but it ain’t what want! So lets first delete everything that is inside out main.dart file, and just have this:

    这个程序很有趣,但是不是想要的! 因此,首先删除main.dart文件内的所有内容,然后执行以下操作:

    main.dart file after removing the code for default app 删除默认应用程序的代码后的main.dart文件

    This code simply gives us a text in center of the screen that screams “Hello World”!

    这段代码只是在屏幕中央给我们一个文本,尖叫着“ Hello World”!

    Output of Current Code 当前代码输出

    However, we do not want this output.

    但是,我们不需要此输出。

    First, inside the GitPages.dart file, create a stateful widget with the name GitProfile (Don’t Forget to Import the Material Library)

    首先,在GitPages.dart文件中,创建一个名为GitProfile的有状态小部件(不要忘记导入材质库)

    GitProfile.dart file after creating Stateful Widget 创建状态小部件后的GitProfile.dart文件

    To make the app look beautiful and add an appbar, remove the Container form the Widget tree and instead return a Scaffold with an AppBar class whose title is “GITHUB PROFILE” and the property centerTitle set to true

    为了使应用看起来更漂亮并添加一个应用栏,请从“小部件”树中删除“容器”,然后返回一个带有AppBar类的Scaffold,其标题为“ GITHUB PROFILE”,并且将centerTitle属性设置为true

    GitProfile.dart file after adding Scaffold widget 添加Scaffold小部件后的GitProfile.dart文件

    We need to define different widgets into different functions that returns a widget. The most simple one is a Circular Progress Bar. Lets start with it.

    我们需要将不同的小部件定义为返回小部件的不同函数。 最简单的一个是循环进度栏。 让我们开始吧。

    Change the code in main.dart with this:

    使用以下命令更改main.dart中的代码:

    GirProgile.dart file after creating a function for Loading Widget 创建用于加载小部件的功能后的GirProgile.dart文件

    We still need to view the changes we did in our emulator. However, even if we run the app now, we don’t see any changes in the screen. That is because the GitProfile widget hasn’t been called in the main.dart file of our app. In Main.dart file, remove the Center widget and replace it with GitProfile widget. (Don’t forget to import it!!)

    我们仍然需要查看我们在模拟器中所做的更改。 但是,即使我们现在运行该应用程序,我们也不会在屏幕上看到任何更改。 这是因为尚未在我们应用的main.dart文件中调用GitProfile小部件。 在Main.dart文件中,删除“中心”窗口小部件并将其替换为GitProfile窗口小部件。 (别忘了导入!!)

    main.dart file after adding GitProfile to home of the app. 将GitProfile添加到应用主目录后的main.dart文件。

    Now if we run the app, we should see a loading widget in the center of our screen.

    现在,如果我们运行该应用程序,我们应该在屏幕中央看到一个正在加载的小部件。

    Output 输出量

    The other widget that we require is a TextField widget. However, the styling of this widget might require the code to be long. Hence, we will create a new file TextField.dart inside the widgets folder and we will create a Stateless class that returns our text form field.

    我们需要的另一个小部件是TextField小部件。 但是,此小部件的样式可能需要较长的代码。 因此,我们将在widgets文件夹中创建一个新文件TextField.dart,并创建一个Stateless类,该类返回我们的文本表单字段。

    Updated File Structure in Lib folder 更新了Lib文件夹中的文件结构

    Inside TextField.dart file, create a stateless widget with name TextField that returns a Expanded widget with a Container having a BorderRadius and the Container will have a child of TextField with decoration having no border, a suffix icon and a hintText.

    在TextField.dart文件中,创建一个名称为TextField的无状态窗口小部件,该无状态窗口小部件将返回一个Expanded窗口小部件,其中Container带有BorderRadius,并且Container将具有TextField的子代,该子代的装饰不带有边框,后缀图标和hintText。

    TextField.dart file TextField.dart文件

    Now to view this in our application, create a method in GitProfile.dart file that returns a Widget and name the method buildInputField() and return the TextField widget from it (Don’t forget to Import it!!)

    现在,要在我们的应用程序中查看此内容,请在GitProfile.dart文件中创建一个返回Widget的方法,并将其命名为buildInputField()并从中返回TextField Widget (不要忘记导入它!)

    Updated GitProfile.dart file 更新了GitProfile.dart文件

    This should Output the following screen:

    这应该输出以下屏幕:

    Output 输出量

    Here we can see that the background of the application is plain white, which alters the TextField color. Thus, in scaffold and in AppBat of the app, add a background color which you like. Ill be adding a Teal color.

    在这里,我们可以看到应用程序的背景是纯白色,这会更改TextField的颜色。 因此,在支架和应用程序的AppBat中,添加所需的背景色。 会增加蓝绿色。

    Output 输出量

    Now, after this, we need to display the user’s information in the screen as well. For it, We need a widget to display UserImage, a Text widget to display UserName and Bio and the additional statistics will be stored in a Row containing three columns with their respective Information.

    现在,在此之后,我们还需要在屏幕上显示用户信息。 为此,我们需要一个小部件来显示UserImage,一个文本小部件来显示UserName和Bio,其他统计信息将存储在包含三列及其相应信息的行中。

    Inside the Widgets folder, create a file named as buildUserData.dart. This file will contain a method buildUserData that will return a column containing data of a particular user.

    在Widgets文件夹中,创建一个名为buildUserData.dart的文件。 该文件将包含一个buildUserData方法,该方法将返回一个包含特定用户数据的列。

    In buildUserData.dart file, add following code:

    在buildUserData.dart文件中,添加以下代码:

    To apply this into the application, in GitPages.dart, in the body property of Scaffold widget, replace buildInputField() with buildUserData().

    要将其应用到应用程序中,请在GitPages.dart中的Scaffold小部件的body属性中,将buildInputField()替换为buildUserData()。

    Updated GitPages.dart file 更新了GitPages.dart文件

    If you run the app, the Output should look like this:

    如果运行应用程序,则输出应如下所示:

    Output 输出量

    The only thing left to do is bind the states that come from the stream via ProfileBloc class and display them in the UI. To achieve this, we first need to provide the incomming BLoC to the root of out widget. Here, the root widget is inside main.dart file where GitProfile class is being called. Hence, we should pass the BLoC there as form that root widget, all other widgets will get he incoming states.

    剩下要做的唯一事情就是通过ProfileBloc类绑定来自流的状态,并将其显示在UI中。 为此,我们首先需要将传入的BLoC提供给out小部件的根。 在这里,根窗口小部件位于main.dart文件中,其中正在调用GitProfile类。 因此,我们应该将BLoC作为根小部件的形式传递到那里,所有其他小部件都将获得传入状态。

    Wrap the GitProfile() with a BLoCProvider. The BLoC provider class has a create property, that is of generic type. In create property we will be passing a function with context which will return the ProfileBloc class. Now we know that ProfileBloc accepts an object of DataRepository class in its constructor as we added it in profile_bloc file, thus we pass DataRepository() in constructor of ProfileBloc.

    用BLoCProvider包裹GitProfile()。 BLoC提供程序类具有通用类型的create属性。 在create属性中,我们将传递一个带有上下文的函数,该函数将返回ProfileBloc类。 现在我们知道ProfileBloc在其构造函数中接受了DataRepository类的对象,因为我们将其添加到profile_bloc文件中,因此我们在ProfileBloc的构造函数中传递了DataRepository()。

    Final Update to Main.dart file 对Main.dart文件的最终更新

    After providing the Bloc to the RootWidget, we can consume it through the GitPages class.

    将Bloc提供给RootWidget之后,我们可以通过GitPages类使用它。

    In GitWidget Class, we need to determine which widget to display on various state. For it, we need to consume in incoming BLoC from the main.dart file. To do it, lets first make a ListView as the data in the View might exceed the viewport’s height. The ListView widget accepts children as the parameter, where we provide a container with a certain height. The only children of this Listview will be a container with a certain height set for all our widgets to reside upon. Now, the child of our container must change depending on the incoming state from out BLoC.

    在GitWidget类中,我们需要确定要在各种状态下显示的窗口小部件。 为此,我们需要使用main.dart文件中的传入BLoC。 为此,首先创建一个ListView,因为View中的数据可能会超过视口的高度。 ListView小部件接受子代作为参数,我们在其中提供一定高度的容器。 此Listview的唯一子级将是一个具有一定高度的容器,所有我们的小部件都将驻留在该高度上。 现在,我们的容器的子代必须根据BLoC发出的传入状态进行更改。

    In the Child of the Container, we first consume the BLoC using BlocConsumer() class. This class is also of generic type, which accepts the actual bloc and the state of the application. The state of the application are inherited from the class ProfileState and the BLoC is defined in ProfileBloc class. We pass the instances of these classes here.

    在容器的子级中,我们首先使用BlocConsumer()类使用BLoC。 该类也是通用类型,它接受实际的块和应用程序的状态。 应用程序的状态是从类ProfileState继承的,并且BLoC是在ProfileBloc类中定义的。 我们在这里传递这些类的实例。

    BlocConsumer has two parameters, builder and listener. Builder is for building a certain widget based on certain state and listener is to listen for a particular state so return a certain widget.

    BlocConsumer有两个参数,builder和listener。 Builder用于基于特定状态构建特定窗口小部件,而监听器用于侦听特定状态,因此返回特定窗口小部件。

    Lets work in the listener parameter first. We know that the exception that occurs is UserNotFoundException() which is handled by ProfileError State. So, in Listener parameter, we need to check if the state is a ProfileError state, and if it is true, then we return a snackbar with content error, defined under the ProfileError class.

    首先让我们在listener参数中工作。 我们知道发生的异常是UserNotFoundException(),由ProfileError State处理。 因此,在Listener参数中,我们需要检查状态是否为ProfileError状态,如果为true,则返回在ProfileError类下定义的带有内容错误的小吃店。

    In builder parameter however, we need to check if the state is ProfileInitial state, or ProfileLoading state or ProfileLoaded state. In these three cases, we assign a certain widget to them.

    但是,在builder参数中,我们需要检查状态是否为ProfileInitial状态,ProfileLoading状态或ProfileLoaded状态。 在这三种情况下,我们给它们分配了一个小部件。

    If none of these is true, then the state is ProfileState in which case we have to build the ProfileInitial state that only has a TextFormField.

    如果所有这些都不成立,则状态为ProfileState,在这种情况下,我们必须构建仅具有TextFormField的ProfileInitial状态。

    Lets code it in this way:

    让我们以这种方式编写代码:

    Now here, we see that listen parameter listens to a particular state, and if that state is a ProfileError, it displays snack bar.

    现在在这里,我们看到listen参数侦听特定状态,如果该状态是ProfileError,则显示小吃店。

    In Builder parameter, we see that based on each incoming state, we build the respective widget. Now one final thing left to do is as we see in buildUserData method, we have passed a profile of the user. This is because, we need to display the data of that user in screen. Hence, the file buildUserData.dart, the method buildUserData() lets accept a Profile.

    在Builder参数中,我们看到基于每个传入状态,我们都构建了各自的小部件。 现在剩下要做的最后一件事就是在buildUserData方法中看到的,我们已经传递了用户的配置文件。 这是因为,我们需要在屏幕上显示该用户的数据。 因此,文件buildUserData.dart和方法buildUserData()可以接受配置文件。

    And finally to display data of users, in the Text Widgets replace the following values with:

    最后,为了显示用户数据,在“文本小组件”中将以下值替换为:

    “Username” with profile.name

    带有profile.name的“用户名”

    “https://source.unsplash.com/600x600” with profile.image

    带有profile.image的“ https://source.unsplash.com/600x600”

    “User Bio” with profile.bio

    带有profile.bio的“ User Bio”

    “30” with profile.followers.toString()

    带有profile.followers.toString()的“ 30”

    “30” with profile.following.toString()

    带有profile.following.toString()的“ 30”

    “30” with profile.public_repos.toString()

    带有profile.public_repos.toString()的“ 30”

    After this, we have to do one minor thing and that is submitting the username. For it, in TextField.dart file, we have to create a controller fot text controller so that we can pass data. To submit the data, we have to create a method submitUserName that accepts the Context and userName of the user. In the method, we first define the bloc to the incomming bloc, and we add the username via the Event GetUser to the stream. The event then goes to stream to Bloc where it is converted into state and then is sent back to the UI.

    此后,我们要做一件事,那就是提交用户名。 为此,我们必须在TextField.dart文件中创建一个文本控制器,以便我们可以传递数据。 要提交数据,我们必须创建一个方法submitUserName,该方法接受用户的Context和userName。 在该方法中,我们首先将块定义为传入的块,然后通过事件GetUser将用户名添加到流中。 然后,事件将流传输到Bloc,在该事件中它将转换为状态,然后发送回UI。

    Final TextField.dart file 最终的TextField.dart文件

    After all this is done, we are ready to test the application. Restart the app in the emulator, and type your name in the text field to see your stats!!!!!!!!!!!

    完成所有这些步骤后,我们准备测试该应用程序。 在模拟器中重新启动该应用程序,然后在文本字段中键入您的姓名以查看统计信息!

    Creating the simplest of application like fetching User Data from API seems to be very over the top for applying BLoC to. However, we must know that in larger scale applications, these menial tasks happen frequently, and it is very important to manage states properly for the application to function.

    创建最简单的应用程序(如从API提取用户数据)似乎是将BLoC应用于其中的首要任务。 但是,我们必须知道,在大型应用程序中,这些繁琐的任务经常发生,因此正确管理状态以使应用程序正常运行非常重要。

    Thank You Very much for reading the article! If any part of it confuses you, you can as me anytime via email at suparthnarayanghimire2014@gmail.com or even in the comments!!!!

    非常感谢您阅读本文! 如果其中的任何部分使您感到困惑,您可以随时通过电子邮件发送至我,电子邮件地址为suparthnarayanghimire2014@gmail.com甚至在评论中!!!

    Code: https://github.com/suparthghimire/github_profile

    代码: https : //github.com/suparthghimire/github_profile

    Signing Out!! Adios!✌✌

    登出!! Adios!✌✌

    翻译自: https://medium.com/@suparthnarayanghimire2014/simple-github-profile-page-display-app-using-github-api-and-flutter-using-bloc-pattern-62513f1cfbcb

    相关资源:微信小程序源码-合集6.rar
    Processed: 0.016, SQL: 8