Repository: flutter
Issue: [webview_flutter] `PlatformException(NullPointerException, java.lang.NullPointerException: Attempt to invoke virtual method 'void android.webkit.WebView.loadUrl(java.lang.String, java.util.Map)' on a null object reference, null, null)`
Labels: c: crash,platform-android,p: webview,package,P2,team-android,triaged-android

### Problem description

When using WebView, even just the simplest usage `WebView(initialUrl: ...)`, an NPE happens sometimes.

stack trace:

<details>

![image](https://user-images.githubusercontent.com/5236035/199498046-4c44f515-715a-474c-99a5-e514c9141d75.png)

</details>

### Cause analysis

This seems to be caused by a racing condition. Consider the following simplified scenario: What happens if a WebView widget is in the tree and soon disposed? Let's look at source code:

The widget:

```dart
class WebView extends StatefulWidget {
  final String initialUrl;
```

```dart
class _WebViewState extends State<WebView> {
  @override
  Widget build(BuildContext context) {
    return WebView.platform.build(
      creationParams: _creationParamsfromWidget(widget),

CreationParams _creationParamsfromWidget(WebView widget) {
  return CreationParams(
    initialUrl: widget.initialUrl,
```

So need to see WebView.platform.build:

```dart
class AndroidWebView implements WebViewPlatform {
  Widget build({
    required CreationParams creationParams,
  }) {
    return WebViewAndroidWidget(
      creationParams: creationParams,

class WebViewAndroidWidget extends StatefulWidget {}

class _WebViewAndroidWidgetState extends State<WebViewAndroidWidget> {
  void initState() {
    controller = WebViewAndroidPlatformController(
      creationParams: widget.creationParams,
  ...

  void dispose() {
    controller._dispose();
  }
```

So we know, initState creates a controller, and dispose calls its `_dispose`.

```dart
class WebViewAndroidPlatformController extends WebViewPlatformController {
  WebViewAndroidPlatformController({
    required CreationParams creationParams, ...
  }) {
    webView = webViewProxy.createWebView(
      useHybridComposition: useHybridComposition,
    );
    ...

    if (initialUrl != null) {
      loadUrl(initialUrl, <String, String>{});
    }
  }

  Future<void> loadUrl() {
    return webView.loadUrl(url, headers ?? <String, String>{});
  }

  Future<void> _dispose() => webView.release();
```

It calls webView.loadUrl, webView.release:

```dart
class WebView extends JavaObject {
  /// Loads the given URL with additional HTTP headers, specified as a map from name to value.
  ///
  /// Note that if this map contains any of the headers that are set by default
  /// by this WebView, such as those controlling caching, accept types or the
  /// User-Agent, their values may be overridden by this WebView's defaults.
  ///
  /// Also see compatibility note on [evaluateJavascript].
  Future<void> loadUrl(String url, Map<String, String> headers) {
    return api.loadUrlFromInstance(this, url, headers);
  }

  /// Releases all resources used by the [WebView].
  ///
  /// Any methods called after [release] will throw an exception.
  Future<void> release() {
    _currentWebViewClient = null;
    WebSettings.api.disposeFromInstance(settings);
    return api.disposeFromInstance(this);
  }
```

What is that api:

```dart
  /// Helper method to convert instances ids to objects.
  Future<void> loadUrlFromInstance(
    WebView instance,
    String url,
    Map<String, String> headers,
  ) {
    return loadUrl(instanceManager.getIdentifier(instance)!, url, headers);
  }

  /// Helper method to convert instances ids to objects.
  Future<void> disposeFromInstance(WebView instance) async {
    final int? instanceId = instanceManager.getIdentifier(instance);
    if (instanceId != null) {
      instanceManager.remove(instanceId);
      await dispose(instanceId);
    }
  }
```

And those `loadUrl` and `dispose` calls Pigeon generated methods. Let's see the other side of Pigeon:

```java
  /** Generated interface from Pigeon that represents a handler of messages from Flutter. */
  public interface WebViewHostApi {
```

```java
public class WebViewHostApiImpl implements WebViewHostApi {
  @Override
  public void loadUrl(Long instanceId, String url, Map<String, String> headers) {
    final WebView webView = (WebView) instanceManager.getInstance(instanceId);
    webView.loadUrl(url, headers);
  }

  @Override
  public void dispose(Long instanceId) {
    final WebView instance = (WebView) instanceManager.getInstance(instanceId);
    if (instance != null) {
      ((Releasable) instance).release();
      instanceManager.remove(instanceId);
    }
  }
```

Ok now we know what happens: 

* When initState, controller is created, and an *async* java loadUrl is called
* When dispose, an *async* java dispose method is called

So consider this:

* widget initState, so start calling java loadUrl
* widget dispose, so start calling java dispose
* *java dispose is triggered and finished firstly, before java loadUrl is run*
* java loadUrl is then triggered. But the `instanceManager.getInstance` is already null

So we get error.