1
头图

公众号名片
作者名片

foreword

In the last article "Sentry's Landing Practice in Hundred Bottles", the author mainly elaborated from two major aspects: solution selection and landing practice. In this article, we mainly focus on the problems encountered by Sentry in the landing practice of 100 bottles analysis. The main problems analyzed in this article mainly include the following categories (Flutter SDK version is 1.22.6, Dart SDK version is 2.10.5):

  • NoSuchMethodError
  • Flutter official bug (fixed)
  • StateError
  • NetworkError(DNS)

NoSuchMethodError

Question one

Problem Description:

When judging data of types such as List and String, xxx.isNotEmpty is used directly, and no judgment is made as to whether it is null, resulting in NoSuchMethodError: The getter isNotEmpty was called null.

Screenshot of the problem:

sentry_no_such_method_error_1
sentry_no_such_method_error_2

solution:

 // 问题代码
if(timeEndList.isNotEmpty){
    ...
}
// 解决方案
static bool isNotNullOrEmpty<E>(Iterable<E> iterable) => iterable != null && iterable.isNotEmpty;

if (IterableUtils.isNotNullOrEmpty(timeEndList)){
    ...
}

When judging null, we need to first judge whether it is null, and then use isNotEmpty to judge to avoid this type of error. Considering that we will use a lot of similar judgments in the project, we can judge the same type of data. Encapsulate to avoid having to write it again for every use.

Question two

Problem Description:

Here, Future.wait is used to concurrently request multiple APIs, and a timeout is set in the second API. Since the second API request timed out, when the response is subsequently processed, the null exception judgment is not processed, so that the code cannot be obtained.

Screenshot of the problem:

sentry_no_such_method_error_3
sentry_no_such_method_error_4

solution:

 // 问题代码
if (res[1].code == HttpCode.ok) {
  ...
}

// 解决方案
if (res[1]?.code == HttpCode.ok) {
  ...
}

When using Future.wait to concurrently request multiple APIs, if timeout processing is set, consider the problem of API request timeout failure and try to avoid this problem.

Question three

Problem Description:

An error occurred when we needed to get the size or position of the RenderBox associated with the Widget context.

Screenshot of the problem:

sentry_no_such_method_error_5
sentry_no_such_method_error_6

solution:

 // 问题代码
if (IterableUtils.isNotNullOrEmpty(ctx.state.details) == true) {
  final RenderBox renderBox = ctx.state.detailsKey.currentContext.findRenderObject();
  final Offset postion = renderBox.localToGlobal(Offset.zero);
  ctx.dispatch(MallGoodsDetailActionCreator.setDetailsOffsetYAction(postion.dy));
}

// 解决方案
if (IterableUtils.isNotNullOrEmpty(ctx.state.details) == true) {
  WidgetsBinding.instance.addPostFrameCallback((_) {
    final RenderBox renderBox = ctx.state.detailsKey.currentContext.findRenderObject();
    final Offset postion = renderBox.localToGlobal(Offset.zero);
    ctx.dispatch(MallGoodsDetailActionCreator.setDetailsOffsetYAction(postion.dy));
  });
}

The reason for the above problem is that the context is not associated with our state. If we want to avoid this situation, we can obtain the size or position of the RenderBox after the Widget is rendered.

Flutter official bug (fixed)

Problem Description:

When using the NestedScrollView component, since position.minScrollExtent can be null, NoSuchMethodError nested_scroll_view.dart in _NestedScrollCoordinator.hasScrolledBody NoSuchMethodError: The method '>' was called on null. Receiver: null Tried calling: >( ) This problem has now been officially resolved and merged into the master branch.

Screenshot of the problem:

sentry_nested_scroll_view_error

So how did this problem happen? The official text is explained as follows:

  1. scheduleAttachRootWidget will call _firstBuild and create a new _NestedScrollPosition with empty pixels;
  2. FocusManager will schedule a microtask;
  3. Complete firstBuild and refresh the microTask, NestedScrollView is dirty again;
  4. scheduleWarmUpFrame will rebuild dirty nodes and trigger an exception (_NestedScrollPosition is only available after layout).

solution:

 // 问题代码
bool get hasScrolledBody {
  for (final _NestedScrollPosition position in _innerPositions) {
    assert(position.minScrollExtent != null && position.pixels != null);
    if (position.pixels > position.minScrollExtent) {
      return true;
    }
  }
  return false;
}

// 解决方案
bool get hasScrolledBody {
  for (final _NestedScrollPosition position in _innerPositions) {
    if (!position.hasContentDimensions || !position.hasPixels) {
      continue;
    } else if (position.pixels > position.minScrollExtent) {
      return true;
    }
  }
  return false;
}

StateError

Problem Description:

When we use list.firstWhere, it usually causes problems such as Bad State: No element.

Screenshot of the problem:

sentry_state_error_1
sentry_state_error_2

solution:

 // 问题代码
Map<String, String> getInitialSkuById(String skuId, List<Map<String, dynamic>> skuList) {
  final Map<String, String> selectedKeyValue = <String, String>{};
  final Map<String, dynamic> selectedSku =
      skuList.firstWhere((Map<String, dynamic> skuItem) => skuItem['id'] == skuId);

  if (selectedSku['stockNum'] > 0) {
    selectedSku.forEach((String k, dynamic v) {
      if (k.contains('keyStr')) {
        selectedKeyValue[k] = v;
      }
    });
  }

  return selectedKeyValue;
}

// 解决方案
Map<String, String> getInitialSkuById(String skuId, List<Map<String, dynamic>> skuList) {
  final Map<String, String> selectedKeyValue = <String, String>{};
  final Map<String, dynamic> selectedSku = skuList.firstWhere(
    (Map<String, dynamic> skuItem) => skuItem['id'] == skuId,
    orElse: null,
  );

  if (selectedSku != null && selectedSku['stockNum'] > 0) {
    selectedSku.forEach((String k, dynamic v) {
      if (k.contains('keyStr')) {
        selectedKeyValue[k] = v;
      }
    });
  }
  return selectedKeyValue;
}

When we use list.firstWhere, there is usually no matching condition. At this time, it is very necessary to use orElse to deal with this situation.

The following code filters the result value of 'green' according to the condition, if not, it returns 'No matching color found', and the result output is: No matching color found.

 final List<String> list = <String>['red', 'yellow', 'pink', 'blue'];
final String item = list.firstWhere(
  (String element) => element == 'green',
  orElse: () => 'No matching color found',
);
print(item); // // No matching color found

If no orElse is written, an exception will be thrown: Unhandled exception: Bad state: No element. Of course, if you are in the Null safety version, you can directly use the firstWhereOrNull method for processing.
Let's compare the source code of firstWhere and firstWhereOrNull:

 E firstWhere(bool test(E element), {E orElse()?}) {
  for (E element in this) {
    if (test(element)) return element;
  }
  if (orElse != null) return orElse();
  throw IterableElementError.noElement();
}

T? firstWhereOrNull(bool Function(T element) test) {
  for (var element in this) {
    if (test(element)) return element;
  }
  return null;
}

firstWhere will first match the results that meet the conditions. If there is no match, then the orElse will be processed. If there is no orElse, an exception will be thrown; firstWhereOrNull is much simpler. If there is no matching value, it will return directly null.

NetworkError(DNS)

Network errors are error conditions that cause network requests to fail, each network error has a type, which is a string, and each network error has a stage, which describes in which stage the error occurred:

  1. dns: An error occurred during DNS resolution;
  2. connection: An error occurred during the establishment of a secure connection;
  3. application: Errors occurred during request and response transmission;

Problem Description:

When the client initiates a network request to the service order, it will go through the process of DNS resolution. Generally, it is the traditional way to initiate a resolution request to the operator Local DNS based on the DNS protocol. However, in this case, domain name hijacking and cross-border DNS may occur. Internet access problems, resulting in abnormal domain name resolution.

sentry_network_error_1

solution:

So, what should we do if our App finds that DNS resolution fails when it initiates a network request? Of course, we can access services such as Alibaba Cloud DNS DNS service or Tencent Mobile DNS DNS service to more effectively ensure normal access to apps and applets.

Let's review the knowledge related to DNS together:

  • What is DNS
  • Domain Name Hierarchy
  • DNS Hierarchy
  • DNS resolution process

DNS

DNS is the abbreviation of Domain Name System (Domain Name System), which is a core service of the Internet. As a distributed database that can map domain names and IP addresses to each other, it can make it easier for people to access the Internet without having to remember them. A machine-readable IP string.

Domain Name Hierarchy

Due to the large number of Internet users, all Internets are named using a hierarchical tree structure.
Any host or router connected to the Internet has a unique hierarchy (domain name).
Domain names can be divided into subdomains, and subdomains can be further divided into subdomains of subdomains, thus forming top-level domain names, main domain names, and subdomains.
  1. ".com" is the top level domain;
  2. "baiping.com" is the main domain name (also known as a hosted first-level domain name), mainly referring to the corporate page name;
  3. "example.baiping.com" is a subdomain (also called a hosted second-level domain);
  4. "www.example.baiping.com" is a subdomain of a subdomain (also known as a hosted third-level domain).

sentry_network_error_2

DNS Hierarchy

The domain name is a hierarchical structure, and the domain name DNS server is also a corresponding hierarchical structure. With the domain name structure, a domain name DNS server is also required to resolve the domain name, and it needs to be resolved by the domain name DNS server all over the world. The domain name DNS server is actually a host with a domain name system.

sentry_network_error_3

DNS resolution process

The results of the DNS query are usually cached in the local domain name server. If there is a cache in the local domain name server, the following DNS query steps will be skipped and the resolution results will be returned soon. The 8 steps required for a DNS query without a local name server cache:

  1. When a user enters "example.com" in a web browser, the local domain name server starts a recursive query.
  2. The local domain name server uses an iterative query method to query the root domain name server;
  3. The root domain name server tells the local domain name server the IP address of the top-level domain name server .com TLD (top-level domain name server) that should be queried next;
  4. The local domain name server queries the top-level domain name server .com TLD;
  5. The .com TLD server tells the local domain name server to query the IP address of the authoritative domain name server for example.com;
  6. The local name server sends a query to the example.com authoritative name server;
  7. The example.com authoritative name server tells the local name server the IP address of the host queried;
  8. The local domain name server finally responds to the web browser with the queried IP address. Once the 8 steps of the DNS lookup returned the IP address of example.com, the browser was able to make a request for the web page;
  9. The browser makes an HTTP request to the IP address;
  10. The web server at this IP returns the web page to be rendered in the browser.

Glossary:

  1. DNS Resolve: Refers to the local domain name server, which is the first stop in a DNS lookup and is the DNS server responsible for processing the initial request. The DNS assigned by the operator's ISP, Google 8.8.8.8, etc. belong to DNS Resolver;
  2. Root Server: refers to the root domain name server. When the local domain name server cannot find the resolution result locally, the first step will be to query it and obtain the IP address of the top-level domain name server;
  3. Recursive query: It means that the DNS server must return an accurate query result to the user when it receives the request initiated by the user. If the DNS server does not store the corresponding information locally, the server needs to query other servers and submit the returned query structure to the user;
  4. Iterative query: It means that when the DNS server receives the request initiated by the user, it does not directly reply to the query result, but tells the address of another DNS server, and the user submits the request to this DNS server, and so on and so forth until it returns search result.

sentry_network_error_4

Summarize

The above four exceptions are the problems we often encounter in the early stage of writing code. Through the analysis of the above four exceptions, we can get some experience summaries. In the subsequent development, we can make improvements based on these summaries for better of solving the problem.

For more exciting things, please pay attention to our public account "Hundred Bottles Technology", there are irregular benefits!


百瓶技术
127 声望18 粉丝

「百瓶」App 技术团队官方账号。