Skip to content

goBack doesn't work properly with canGoBack if redirected from facebook iOS #2585

Open
@philipd97

Description

@philipd97

Is there an existing issue for this?

  • I have searched the existing issues

Current Behavior

When facebook url finish loading, canGoBack return "true" but when tap on goBack, it doesn't do anything. Should return "false" if it's not able to go back to previous page. This doesn't happen to other url like instragram/youtube/other websites.

Expected Behavior

canGoBack return "false" like other redirected url eg: instagram. And let the navigation router pop the page.

Steps with code example to reproduce

Code Sample
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart' as iwebv;
import 'package:url_launcher/url_launcher_string.dart';
import 'package:webview_flutter/webview_flutter.dart' as webv;
import 'package:url_launcher/url_launcher.dart';

const url = 'https://facebook.com/facebook';
// TODO: change to instagram link, on goBack able to work.
// const url = 'https://www.instagram.com/facebook/';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'FB Navigation Test',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      initialRoute: '/',
      routes: {
        '/': (context) => const MyHome(),
        '/inappwebview': (context) => const TestInAppWebView(),
        '/webview': (context) => const TestWebView(),
      },
    );
  }
}

class MyHome extends StatelessWidget {
  const MyHome({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            TextButton(
              child: const Text('InAppWebView'),
              onPressed: () {
                Navigator.of(context).pushNamed('/inappwebview');
              },
            ),
            const SizedBox(height: 10.0),
            TextButton(
              child: const Text('WebView'),
              onPressed: () {
                Navigator.of(context).pushNamed('/webview');
              },
            ),
          ],
        ),
      ),
    );
  }
}

Future<bool> handleAndroidIntent(String url, Function(String fallbackUrl) reloadUrl) async {
  try {
    String? deepLink;
    RegExp schemeRegex = RegExp(r'scheme=([\w\.]+)');
    Match? match = schemeRegex.firstMatch(url);

    if (match != null) deepLink = match.group(1);

    // Extract fallback URL
    String? fallbackUrl;
    RegExp fallbackRegex = RegExp(r'S\.browser_fallback_url=([^;]+)');
    Match? fallbackMatch = fallbackRegex.firstMatch(url);

    if (fallbackMatch != null) {
      fallbackUrl = Uri.decodeFull(fallbackMatch.group(1)!);
    }

    // Convert intent:// to fb:// or appropriate deep link
    if (deepLink != null && deepLink == "fb") {
      String fbDeepLink = url.replaceAll("intent://", "fb://");

      if (await canLaunchUrlString(fbDeepLink)) {
        await launchUrlString(fbDeepLink);
        return false;
      } else if (fallbackUrl != null) {
        reloadUrl(fallbackUrl);
        return false;
      }
    } else if (fallbackUrl != null) {
      // If not Facebook but there's a fallback URL, open it
      reloadUrl(fallbackUrl);
      return false;
    }
    return true;
  } catch (e) {
    print("Error handling intent:// URL: $e");
    return true;
  }
}

class TestInAppWebView extends StatefulWidget {
  const TestInAppWebView({super.key});

  @override
  State<TestInAppWebView> createState() => _TestInAppWebViewState();
}

class _TestInAppWebViewState extends State<TestInAppWebView> {
  iwebv.InAppWebViewController? _inAppWebViewController;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('InAppWebViewTest')),
      floatingActionButton: Builder(builder: (context) {
        return FloatingActionButton(
          child: const Icon(Icons.arrow_back),
          onPressed: () async {
            // Back on webview or pop page.
            final canBack = await _inAppWebViewController?.canGoBack() ?? false;
            print('canBack InAppwebview: $canBack');
            openSnackBar(context, canBack);
            canBack ? await _inAppWebViewController?.goBack() : Navigator.pop(context);
          },
        );
      }),
      body: iwebv.InAppWebView(
        onWebViewCreated: (ctl) => _inAppWebViewController = ctl,
        initialSettings: iwebv.InAppWebViewSettings(transparentBackground: true),
        initialUrlRequest: iwebv.URLRequest(url: iwebv.WebUri(url)),
        shouldOverrideUrlLoading: (webviewController, navigationAction) async {
          var uri = navigationAction.request.url!;

          /// Handle on intent://
          if (!["http", "https", "file", "chrome", "data", "javascript", "about"].contains(uri.scheme)) {
            if (uri.scheme == 'intent' && Platform.isAndroid) {
              final result = await handleAndroidIntent(
                uri.toString(),
                (fallbackUrl) =>
                    webviewController.loadUrl(urlRequest: iwebv.URLRequest(url: iwebv.WebUri(fallbackUrl))),
              );
              return result ? iwebv.NavigationActionPolicy.ALLOW : iwebv.NavigationActionPolicy.CANCEL;
            }
            if (await canLaunchUrl(uri)) await launchUrl(uri);
            return iwebv.NavigationActionPolicy.CANCEL;
          }

          return iwebv.NavigationActionPolicy.ALLOW;
        },
      ),
    );
  }
}

class TestWebView extends StatefulWidget {
  const TestWebView({super.key});

  @override
  State<TestWebView> createState() => _TestWebViewState();
}

class _TestWebViewState extends State<TestWebView> {
  late final webv.WebViewController _webViewWidgetController = webv.WebViewController()
    ..setJavaScriptMode(webv.JavaScriptMode.unrestricted)
    ..loadRequest(Uri.parse(url))
    ..setNavigationDelegate(
      webv.NavigationDelegate(
        onNavigationRequest: (request) async {
          Uri uri = Uri.parse(request.url);

          /// Handle on intent://
          if (!["http", "https", "file", "chrome", "data", "javascript", "about"].contains(uri.scheme)) {
            if (uri.scheme == 'intent' && Platform.isAndroid) {
              final result = await handleAndroidIntent(
                uri.toString(),
                (fallbackUrl) => _webViewWidgetController.loadRequest(Uri()),
              );
              return result ? webv.NavigationDecision.navigate : webv.NavigationDecision.prevent;
            }

            if (await canLaunchUrl(uri)) await launchUrl(uri);
            return webv.NavigationDecision.prevent;
          }

          return webv.NavigationDecision.navigate;
        },
      ),
    );

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('WebViewTest')),
      floatingActionButton: FloatingActionButton(
        child: const Icon(Icons.arrow_back),
        onPressed: () async {
          // Back on webview or pop page.
          final canBack = await _webViewWidgetController.canGoBack();
          print('canBack WebView: $canBack');
          openSnackBar(context, canBack);
          canBack ? await _webViewWidgetController.goBack() : Navigator.pop(context);
        },
      ),
      body: webv.WebViewWidget(controller: _webViewWidgetController),
    );
  }
}

void openSnackBar(BuildContext context, bool canBack) {
  ScaffoldMessenger.of(context).hideCurrentSnackBar();
  ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('Can go back -> $canBack')));
}

Flutter version

v3.24.5

Operating System, Device-specific and/or Tool

iOS.
Android need to spam goBack few times only able to work.

Plugin version

v6.1.5

Additional information

I've attached the sample code using webview_flutter plugin also, it's behaviour is same as flutter_inappwebview, might the the way Facebook handle the redirection.

I've logged out the getCopyBackForwardList(), saw that when redirection finish. I'm in the index 1 instead of 0. Not sure if this helps.

Is it possible that I'll have to apply workaround specifically just for facebook navigation?

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions