flutter app
Part of the ‘A Work in Progress’ Series
“ 正在进行 的 工作 ”系列的一部分
Today, I’m demonstrating how the MVC framework library package is adaptive and flexible in its implementation of specific needs. In this case, I’ll show you how the framework easily incorporates Firebase’s Crashlytics for its error handling. Of course, it has its own error handling routines, but the developer has the means to incorporate their own or third-party options.
今天,我将演示MVC框架库程序包在实现特定需求时如何适应性和灵活性。 在这种情况下,我将向您展示该框架如何轻松地将Firebase的Crashlytics纳入其错误处理。 当然,它具有自己的错误处理例程,但是开发人员可以采用自己的方法或第三方选项。
进行中的工作 (A Work In Progress)
This is part of the ‘A Work in Progress’ series of articles covering the progress of a simple ‘ToDo’ app called WorkingMemory. The intent of this series is to document the implementation of every and all aspects of this app and its construction as well as its use of the MVC framework library package, mvc_application.
这是“ 正在进行 的 工作 ”系列文章的一部分,该系列文章介绍了名为WorkMemory的简单“ ToDo”应用程序的进度。 本系列的目的是记录该应用程序各个方面的实现,其构造以及对MVC框架库包mvc_application的使用 。
我喜欢截图。 单击要点。 (I Like Screenshots. Click For Gists.)
As always, I prefer using screenshots over gists to show concepts rather than just show code in my articles. I find them easier to work with, and easier to read. However, you can click/tap on them to see the code in a gist or in Github. Ironically, it’s better to read this article about mobile development on your computer than on your phone. Besides, we program on our computers; not on our phones. For now.
与往常一样,我更喜欢使用屏幕截图而不是要点来显示概念,而不是仅在文章中显示代码。 我发现它们更易于使用和阅读。 但是,您可以单击/点击它们以查看要点或Github中的代码。 具有讽刺意味的是,最好在计算机上阅读这篇文章,而不是在手机上阅读有关移动开发的文章。 此外,我们在计算机上编程; 不在我们的手机上。 目前。
Let’s begin.
让我们开始。
Let’s start from the beginning, and when it comes to Flutter apps, that means starting in the function, main(). You can see the app, WorkingMemory, is being passed to the runApp() function as usual. Note, this app extends the class, App. At startup, the class, App, does a lot of stuff ‘behind the scenes’ to set up and ready the application. It provides the many functions and features common to all Flutter apps. Further, it has the function, createView(), so a can ‘hot reload’ can be performed to run the class, View, again for any reason.
让我们从头开始,当涉及Flutter应用时,这意味着从函数main ()开始。 您可以看到应用程序WorkingMemory像往常一样传递给runApp ()函数。 注意,此应用程序扩展了类App。 在启动时,类App会在“幕后”做很多事情来设置和准备应用程序。 它提供了所有Flutter应用程序共有的许多功能。 此外,它具有函数createView (),因此可以出于任何原因执行“热重载”以再次运行类View 。
If you’re familiar with my past work, you know I use the MVC design pattern in most of my apps. I’d know the View is the ‘interface’, the Model is the ‘data’, and the Controller is the logic component of the app. If you like, there is the free article, Flutter + MVC at Last!, to introduce you to how MVC implemented in Flutter using the library packages, mvc_application, and mvc_pattern.
如果您熟悉我过去的工作,那么您就会知道我在大多数应用程序中都使用了MVC设计模式。 我知道视图是“接口”,模型是“数据”,而控制器是应用程序的逻辑组件。 如果愿意,可以免费获得Flutter + MVC的最新文章! ,向您介绍如何使用库软件包mvc_application和mvc_pattern在Flutter中实现MVC。
A further collection of free articles on the subject are listed in the article, MVC in Flutter. Simply put, when it came to the architectures and design patterns currently offered to the Flutter community, I found MVC met my needs.
在Flutter中的MVC文章中列出了关于该主题的更多免费文章。 简而言之,当谈到当前为Flutter社区提供的架构和设计模式时,我发现MVC可以满足我的需求。
Let’s get back to it. Note the import statements in the main.dart file. You’ll see such import statements throughout the source code. With the file, view.dart concerned the app’s interface, and the file, controller.dart, concerned with the logic involved in the app. For example, you can see the class, View, is coming from the file, view.dart. Very reasonable — it’s concerned with the interface. Further, the function, runApp(), is coming from the file, controller.dart. However, that’s not the runApp() function you may be familiar with.
让我们回到它。 注意main.dart文件中的import语句。 您会在整个源代码中看到这些import语句。 对于文件, view.dart与应用程序的界面有关,而文件controller.dart与应用程序所涉及的逻辑有关。 例如,您可以看到View类来自文件view.dart。 非常合理-它与界面有关。 此外,函数runApp ()来自文件controller.dart 。 但是,您可能并不熟悉runApp ()函数。
运行运行运行应用程序 (Run Run Run An App)
The screenshot below presents the screen of my IDE. That’s so you can get an appreciation of how the code is organized in this app. The Controller, in simple terms, is to ‘control’ what happens when something…happens. As pertaining to this article, when the app starts up, the Firebase Crashlytics routine is to handle and record any and all exceptions that may occur.
下面的屏幕截图显示了我的IDE的屏幕。 这样一来,您就可以了解该应用程序中代码的组织方式。 简单来说,控制器就是“控制”某事……发生时发生的事情。 关于本文,当应用程序启动时,Firebase Crashlytics例程将处理并记录可能发生的所有异常。
If you look closely, the runApp() function is found in the ‘Controller’ file situated at the ‘app level.’ In other words, in the file, controller.dart, found in the directory, app. The code in that directory and in that file is concerned with the app ‘on the whole.’ It’s not concerned, for example, with the home screen for this particular app. The code for the home screen is instead be found in the directory, home. This is all in the attempt to organize the code.
如果仔细观察,可以在“应用程序级别”的“控制器”文件中找到runApp ()函数。 换句话说,在目录app中的controller.dart文件中。 该目录和该文件中的代码“整体上”与应用程序有关。 例如,与此特定应用程序的主屏幕无关。 相反,可以在目录home中找到用于主屏幕的代码。 这都是为了组织代码。
Again, looking closely at the runApp() function, we see, if nothing is passed as parameters, the Firebase Crashlytics routines are assigned to handle and report exceptions. The runApp() function displayed above then calls the runApp() functions displayed below. Yet, it too is not the function you’re familiar with. However, this second runApp() function does eventually call the original runApp() function supplied by Flutter, but not before wrapping your app around an error handler. How this error handler works is what we’re going to examine, today.
再次仔细查看runApp ()函数,我们看到,如果没有任何内容作为参数传递,则将Firebase Crashlytics例程分配给处理和报告异常。 上面显示的runApp ()函数然后调用下面显示的runApp ()函数。 但是,它也不是您熟悉的功能。 但是,第二个runApp ()函数最终会调用Flutter提供的原始runApp ()函数,但不会在将应用程序包装到错误处理程序之前将其调用。 今天,我们将研究该错误处理程序的工作方式。
在区域 (In The Zone)
We’re going to walk-through the code and examine the series of events that occur when an error is triggered in the app. This error will occur when the runApp() function is running and the app is first starting up. In the screenshot above, you can see the error routine, runZonedError, is highlighted. It’s called when an error occurs at startup.
我们将遍历代码并检查在应用程序中触发错误时发生的一系列事件。 当runApp ()函数正在运行并且应用程序首次启动时,将发生此错误。 在上面的屏幕截图中,您可以看到错误例程runZonedError突出显示。 启动时发生错误时调用。
In the screenshot below, you’ll notice I’ve commented-out the Firebase Crashlytics routines for now. Hence, they won't be used in this instance. Let’s instead see what the ‘default behaviour’ is when an error occurs at startup.
在下面的屏幕截图中,您会注意到我现在已经注释掉了Firebase Crashlytics例程。 因此,它们不会在这种情况下使用。 相反,让我们看看启动时发生错误时的“默认行为”是什么。
Below, is a screenshot of the error routine, runZonedError. When an exception does occur, it merely passes the generated Exception and StackTrace objects to the routine, _debugReportException(). It is this routine that then produces a ‘FlutterErrorDetails’ object and finally passes it to the Flutter framework’s error handler. Of course, since this app uses the MVC framework, the developer is able to define their own error handler.
下面是错误例程runZonedError的屏幕截图。 当确实发生异常时,它将仅将生成的Exception和StackTrace对象传递给例程_debugReportException ()。 正是这个例程产生了一个'FlutterErrorDetails'对象,最后将其传递给Flutter框架的错误处理程序。 当然,由于此应用程序使用MVC框架,因此开发人员可以定义自己的错误处理程序。
犯错误 (To Make An Error)
To cause the error, I’ve decided to mess with the very framework I use with all my apps, and change some code to intentionally cause an error — I’ll be sure to correct it after we’re done with this exercise.
为了引起错误,我决定将所有应用程序使用的框架弄乱,并更改一些代码以有意引起错误-在完成本练习之后,我将确保对其进行纠正。
听连接 (Listen To The Connection)
In this framework, you can assign ‘listeners’ to be triggered if and when the device’s connectivity status changes for one reason or another. For example, when you turn off the wifi or your phone. The class, App, has such a routine to add a listener, and it’s found right in its constructor. As your recall, the App class is extended and called in the main.dart file. A screenshot of this class and its constructor is displayed below.
在此框架中,您可以分配“侦听器”,以在设备的连接状态由于某种原因或其他原因而发生更改时以及触发时触发。 例如,当您关闭wifi或手机时。 App类具有添加侦听器的例程,可以在其构造函数中找到它。 回想一下,App类已扩展并在main.dart文件中调用。 此类及其构造函数的屏幕快照如下所示。
I made a quick change to the code to cause an error at startup. The function, addConnectivityListener(), is called every time the Flutter app starts up. In most instances, there won’t be a ‘listener’ specified and so there’s an if statement there to prevent any problems. You see, one can’t assign null as a listener, but I’ve commented out that if statement. The app is not going to like that.
我对代码进行了快速更改,以在启动时导致错误。 每次Flutter应用启动时,都会调用addConnectivityListener ()函数。 在大多数情况下,不会指定“监听器”,因此这里有一个if语句来防止出现任何问题。 您会看到,不能将null分配为侦听器,但是我已经注释掉了if语句。 该应用程序不会那样。
报告错误 (Report The Error)
And so, in the screenshot below, we’re back where the app is just about to call Flutter’s own error handler. It had started up but quickly encountered that error while running Flutter’s runApp() function. In the screenshot below, we see the FlutterErrorDetails object was created and, again, is just about to be passed to Flutter’s reportError() function. As you know from your own experiences with Flutter, this usually results in the ‘Red Screen of Death’ or at the very least, the error message and stack trace displayed in your IDE’s console screen.
因此,在下面的屏幕截图中,我们回到了该应用程序即将调用Flutter自己的错误处理程序的位置。 它已经启动,但是在运行Flutter的runApp ()函数时很快遇到了该错误。 在下面的屏幕截图中,我们看到FlutterErrorDetails对象已创建,并且将再次传递给Flutter的reportError ()函数。 从Flutter的经验中可以知道,这通常会导致“死亡的红色屏幕”,或者至少会导致错误消息和堆栈跟踪显示在IDE的控制台屏幕中。
Let’s that a quick peek inside Flutter’s reportError() function. You can see that it, in turn, calls the routine assigned to the static variable, onError. Note, the reportError() function is called almost every time an exception occurs in your Flutter app.
让我们快速浏览一下Flutter的reportError ()函数。 您可以看到它依次调用了分配给静态变量onError的例程。 请注意,几乎每当Flutter应用程序中发生异常时,都会调用reportError ()函数。
转储错误 (Dump The Error)
By design, the default behavior for the static function, FlutterError.onError, is to call yet another static function, dumpErrorToConsole. A very descriptive name — you’re familiar with this as it lists the error right out on the IDE’s console screen. In fact, below is a screenshot of this original routine.
根据设计,静态函数FlutterError.onError的默认行为是调用另一个静态函数dumpErrorToConsole 。 一个非常具有描述性的名称-您对它很熟悉,因为它会在IDE的控制台屏幕上立即列出该错误。 实际上,以下是此原始例程的屏幕截图。
However, in this framework, it’s the function below that’s called instead Below is a screenshot with a breakpoint stopping the running code. In this MVC framework, error handling is to be as versatile as possible. For example, the onError() function below could have been overridden by the developer to customize the error handling. However, you can see the ‘App Controller’, con, has its own error handler — a developer could have customized that error handler instead. Options.
但是,在此框架中,下面的函数被调用,而不是下面的屏幕快照,其中包含一个断点来停止正在运行的代码。 在此MVC框架中,错误处理应尽可能多才多艺。 例如,开发人员可能已覆盖了下面的onError ()函数,以自定义错误处理。 但是,您可以看到“应用程序控制器” con具有自己的错误处理程序-开发人员可以改为自定义该错误处理程序。 选项。
Let’s now look at that AppController’s error handler. We can see you if you didn’t override it with your own error routine, it turns to it’s associated State object and it’s error handler. At last count, that’s three places in the framework for error handling to be adapted to specific requirements of a Flutter app. Three places the developer or developers could have overridden. Good to have options.
现在让我们看一下该AppController的错误处理程序。 我们可以看到您是否没有使用自己的错误例程覆盖它,它变成了与之关联的State对象,并且是错误处理程序。 最后,这是错误处理框架中要适应Flutter应用程序特定要求的三个地方。 开发人员可以覆盖三个地方。 很高兴有选择。
By design, in this framework, each State object can have its own error handler. Each State object can have any number of Controllers associated with it, and it’s standard for each Controller to then rely on the State object’s error handler — and that’s what you’re seeing in this code. At this point, the State object can either have its own error handler defined or will simply use the ‘old’ or ‘default’ error handler. As it happens, in this case, it’s the old static function, dumpErrorToConsole, that’s the default error handler. Let’s see how this all works.
通过设计,在此框架中,每个State对象都可以具有自己的错误处理程序。 每个State对象可以具有任意数量的与之关联的Controller,然后每个Controller都必须依赖State对象的错误处理程序,这就是您在此代码中看到的标准。 此时,State对象可以定义自己的错误处理程序,或者仅使用“旧”或“默认”错误处理程序。 碰巧的是,在这种情况下,它是旧的静态函数dumpErrorToConsole ,这是默认的错误处理程序。 让我们看看这一切如何运作。
And so, when the instance variable, handler, is executed in the screenshot above, the old static function, dumpErrorToConsole, is indeed called and the error listed in the IDE’s console window as you see below.
因此,当在上面的屏幕截图中执行实例变量handler时 ,确实会调用旧的静态函数dumpErrorToConsole ,并且该错误在IDE的控制台窗口中列出,如下所示。
Crashlytics转 (Crashlytics Turn)
However, we want the developer to have options, and in this case, the developer wants to use Firebase Crashlytics. Let’s uncomment those two lines and now allow Firebase Crashlytics to take over any and all exceptions that may occur. And so, Crashlytics is now assigned as the error handler as well as assigned to report any exception that may occur.
但是,我们希望开发人员具有选项,在这种情况下,开发人员希望使用Firebase Crashlytics。 让我们取消注释这两行,现在允许Firebase Crashlytics接管所有可能发生的异常。 因此,现在将Crashlytics分配为错误处理程序,并分配为报告可能发生的任何异常。
In this second round, the instance variable, _reportError, in the MVC framework is now not null this time. It now contains a reference to the function, reportError(), in the Crashlytics class.
在第二轮中,这次MVC框架中的实例变量_reportError现在不为空 。 现在,它在Crashlytics类中包含对函数reportError ()的引用。
Below is a screenshot of that very routine in the Crashlytics class. A breakpoint highlights in blue where I’ve stopped the execution. The two red arrows highlight the two functions that were assigned to handle errors. Note, the first arrow conveys the fact Crashlytics’ error handler calls the standard ‘dump to Console’ if an exception occurs.
下面是Crashlytics类中该例程的屏幕截图。 断点以蓝色突出显示,表示我已停止执行。 两个红色箭头突出显示了分配给处理错误的两个功能。 注意,第一个箭头传达了以下事实:如果发生异常,Crashlytics的错误处理程序将调用标准的“转储到控制台”。
发送到Crashlytics (Send To Crashlytics)
Note, setting a particular property to true while in development will send the error information up to your Firebase console as it would do if the app was running in production. And so, it does this even when you're in development and nothing is now displayed on your IDE’s console. Below is a screenshot with the property, endableInDevMode, now set to true.
请注意,在开发过程中将特定属性设置为true即可将错误信息发送到Firebase控制台,就像应用程序在生产环境中运行一样。 因此,即使在开发过程中,它也可以执行此操作,而IDE的控制台上现在不显示任何内容。 下面是具有属性endableInDevMode的屏幕截图,现在将其设置为true。
There’s not much now displayed in the IDE console. Instead, it announces the error information is sent up into the cloud to your Firebase console.
IDE控制台中现在显示的内容很少。 相反,它会宣布错误信息已发送到云端到Firebase控制台。
Mind you, if I had commented out the runApp() routine in this example, the second version of runApp() function (the one that then calls Flutter’s own version) is instead used. And don’t forget, you’re free to impose your own error handling with each StateMVC object you use to display each screen (each View) in your app. Good to have options — especially in your error handling.
请注意,如果在此示例中我注释掉了runApp ()例程,则改用runApp ()函数的第二个版本(然后调用Flutter自己的版本)。 而且请不要忘记,您可以对用于在应用程序中显示每个屏幕(每个视图)的每个StateMVC对象强加自己的错误处理。 拥有选项很高兴-尤其是在您的错误处理中。
Let’s leave it at that. Fork the WorkingMemory app and follow along if you like. Mind you, you’ll have to set up your own Firebase account and database to run it — I’ll help you with that. As things progress, I will be writing supplementary articles about specific aspects of this Flutter app while it uses the MVC design pattern through the library package, mvc_application.
让我们留在那儿。 派生WorkingMemory应用,并根据需要进行操作。 请注意,您必须设置自己的Firebase帐户和数据库才能运行它-我会帮助您。 随着事情的进展,我将编写有关Flutter应用程序特定方面的补充文章,同时它通过库包mvc_application使用MVC设计模式。
Cheers.
干杯。
翻译自: https://medium.com/follow-flutter/error-handling-in-my-flutter-app-1279ec681e90
flutter app