首页 > 解决方案 > 为什么我的 Flutter 页面有时无法在发布版本中完全呈现?

问题描述

问题

我有一个带有登录页面的 Flutter 应用程序。当我在调试模式下运行应用程序时,登录页面会在应用程序打开时正确呈现。但是,当我使用 构建应用程序的 apk 版本flutter build apk --release,安装它然后在模拟器中打开应用程序时,登录页面无法正确呈现。请参阅下面的屏幕截图。

在调试模式下运行,正确渲染:

在此处输入图像描述

运行发布版本,未完全渲染:

在此处输入图像描述

代码

登录页面代码:

// @dart=2.9

import 'dart:async';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart';
// import 'package:myapp/helpers/FCMHelper.dart';
import 'package:myapp/helpers/SecureStorageHelper.dart';
import 'package:myapp/helpers/SharedPreferencesHelper.dart';
import 'package:myapp/models/LoginModel.dart';
import 'package:http/http.dart' as http;
import 'package:myapp/models/Token.dart';
import 'package:myapp/globals.dart' as globals;
import 'package:myapp/models/UserAndTokenModel.dart';
import 'package:myapp/pages/home_page.dart';
import 'package:myapp/pages/reset_password_page.dart';
import 'package:swipedetector/swipedetector.dart';
import 'package:url_launcher/url_launcher.dart';

import '../home_page.dart';

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  final _usernameController = TextEditingController();
  final _passwordController = TextEditingController();
  String loginResponse = "";
  SecureStorageHelper secureStorage;
  bool rememberMe;
  bool isFirstBuild;
  bool isKeyboardVisible = false;
  double displayWidth;
  FocusNode emailFocus;
  FocusNode passwordFocus;
  KeyboardVisibilityController keyboardVisibilityController;

  @override
  void initState() {
    init();
    delayedInit();
    super.initState();
  }

  init() {
    emailFocus = new FocusNode();
    passwordFocus = new FocusNode();
    displayWidth = 1;
    rememberMe = false;
    addKeyBoardListener();
    secureStorage = new SecureStorageHelper();
  }

  delayedInit() {
    Future.delayed(Duration.zero, () {
      getReemberMe();
      tryFillUserCredentials();
      tryAutoLogin();
    });
  }

  getReemberMe() async {
    rememberMe =
        await SharedPreferencesHelper.getBool("myapp_remember_me");
    setState(() {
      rememberMe = rememberMe ?? false;
    });
  }

  tryFillUserCredentials() async {
    var username = await secureStorage.get("myapp_email");
    var password = await secureStorage.get("myapp_password");
    setState(() {
      _usernameController.text = username;
      _passwordController.text = password;
    });
  }

  @override
  Widget build(BuildContext context) {
    hideSystemOverlay();
    setDisplayDimensions();
    return SwipeDetector(
        onSwipeDown: onSwipeDown,
        child: GestureDetector(
          onTap: onScaffoldTap,
          child: Scaffold(
            resizeToAvoidBottomInset: true,
            body: SafeArea(
              child: Stack(
                children: [
                  buildControlsLayer(),
                  buildPolicyLayer(),
                  buildVersionLayer()
                ],
              ),
            ),
          ),
        ));
  }

  buildLogo(int inFlex) {
    return Flexible(
      flex: inFlex,
      child: Image.asset(
        'assets/myapp_logo.png',
        height: MediaQuery.of(context).size.height * 0.20,
      ),
    );
  }

  rememberMeChange(bool newValue) {
    setState(() {
      rememberMe = newValue;
    });
  }

  privacyPolicyTapped() {
    resetFocus();
    launch("https://www.myteam.se/policy.html");
  }

  void resetPasswordPressed() {
    resetFocus();
    Navigator.push(
      context,
      MaterialPageRoute(builder: (context) => ResetPasswordPage()),
    );
  }

  clearUserCredentialTextfields() {
    setState(() {
      _usernameController.clear();
      _passwordController.clear();
    });
  }

  void loggaInPressed() async {
    resetFocus();
    SharedPreferencesHelper.setBool("myapp_remember_me", rememberMe);
    if (!rememberMe)
      deleteUserCredentials();
    else
      storeUserCredentials(_usernameController.text, _passwordController.text);
    var loginModel =
        new LoginModel(_usernameController.text, _passwordController.text);
    var response0 = await postLogin(loginModel);
    if (response0.statusCode == 200) {
      if (!rememberMe) clearUserCredentialTextfields();
      setState(() {
        loginResponse = "";
      });
      var loginToken = Token.fromJson(json.decode(response0.body));
      globals.loginToken = loginToken;
      var response1 = await getUserToken(loginToken.token);
      if (response1.statusCode == 200) {
        var userAndToken =
            UserAndTokenModel.fromJson(json.decode(response1.body));
        var userToken = new Token(token: userAndToken.token);
        var userModel = userAndToken.userModel;
        globals.userToken = userToken;
        globals.userModel = userModel;
        if (rememberMe) storeTokens(loginToken.token, userToken.token);
        // var fcmHelper = new FCMHelper();
        // fcmHelper.configureFCM(context);
        // fcmHelper.registerFCMToken(userModel);
        Navigator.push(
          context,
          MaterialPageRoute(builder: (context) => HomePage()),
        );
      } else
        setState(() {
          loginResponse = "Fel uppstod vid inloggning.";
        });
    } else
      setState(() {
        loginResponse = "Felaktig email eller lösenord.";
      });
  }

  storeUserCredentials(String email, String password) {
    secureStorage.set("myapp_email", email);
    secureStorage.set("myapp_password", password);
  }

  storeTokens(String loginToken, String userToken) {
    secureStorage.set("myapp_login_token", loginToken);
    secureStorage.set("myapp_user_token", userToken);
  }

  deleteUserCredentials() {
    secureStorage.delete("myapp_email");
    secureStorage.delete("myapp_password");
    secureStorage.delete("myapp_login_token");
    secureStorage.delete("myapp_user_token");
  }

  Future<http.Response> postLogin(LoginModel loginModel) {
    String data = json.encode(loginModel);
    return http.post(
        Uri.parse(globals.apiBaseUrl + "authentication/authenticate_mobile"),
        body: data,
        headers: {
          'Content-type': 'application/json',
          'Accept': 'application/json'
        });
  }

  Future<http.Response> postFCMToken(String data) {
    var userToken = globals.userToken.token;
    return http.post(Uri.parse(globals.apiBaseUrl + "/user/set_fcm_token"),
        body: data,
        headers: {
          'Content-type': 'application/json',
          'Accept': 'application/json',
          "Authorization": "Bearer $userToken"
        });
  }

  Future<http.Response> getUserToken(String loginToken) {
    return http
        .get(Uri.parse(globals.apiBaseUrl + "user/get_by_token"), headers: {
      'Content-type': 'application/json',
      'Accept': 'application/json',
      "Authorization": "Bearer $loginToken"
    });
  }

  tryAutoLogin() async {
    var loginToken = await secureStorage.get("myapp_login_token");
    if (loginToken != null) autoLogin();
  }

  autoLogin() async {
    var loginToken = await secureStorage.get("myapp_login_token");
    var response = await getUserToken(loginToken);
    if (response.statusCode == 200) {
      var userAndToken = UserAndTokenModel.fromJson(json.decode(response.body));
      var userToken = new Token(token: userAndToken.token);
      var userModel = userAndToken.userModel;
      globals.userToken = userToken;
      globals.userModel = userModel;
      // var fcmHelper = new FCMHelper();
      // fcmHelper.configureFCM(context);
      // fcmHelper.registerFCMToken(userModel);
      Navigator.push(
        context,
        MaterialPageRoute(builder: (context) => HomePage()),
      );
    } else
      showMessageDialog(
          "Inloggningssessionen utgången", "Logga in igen med dina uppgifter.");
  }

  void showMessageDialog(String title, String body) {
    try {
      showDialog(
          context: context,
          builder: (BuildContext context) {
            return AlertDialog(
              title: new Text(title),
              content: new Text(body),
            );
          });
    } catch (e) {
      print(e.toString());
    }
  }

  void usernameChange(String value) {
    print("");
  }

  void addKeyBoardListener() {
    keyboardVisibilityController = KeyboardVisibilityController();
    keyboardVisibilityController.onChange.listen(onKeyboardVisibilityChange);
  }

  Widget buildUsernameTextfieldAdaptedToKeyboard() {
    return isKeyboardVisible
        ? buildKeyBoardVisibleUsernameTextfield()
        : buildKeyBoardHiddenUsernameTextfield();
  }

  Widget buildPasswordTextfieldAdaptedToKeyboard() {
    return isKeyboardVisible
        ? buildKeyboardVisiblePasswordTextfield()
        : buildKeyboardHiddenPasswordTextfield();
  }

  Widget buildKeyBoardVisibleUsernameTextfield() {
    return Expanded(
      flex: 18,
      child: Container(
        child: Row(
          children: <Widget>[
            Spacer(
              flex: 1,
            ),
            Flexible(
              flex: 8,
              child: TextField(
                  focusNode: emailFocus,
                  controller: _usernameController,
                  autocorrect: false,
                  keyboardType: TextInputType.emailAddress,
                  maxLengthEnforcement: MaxLengthEnforcement.none,
                  onChanged: usernameChange,
                  style: TextStyle(
                      height: 1,
                      fontSize: displayWidth * globals.textFieldFontSize0),
                  decoration: InputDecoration(
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10.0)),
                    filled: true,
                    fillColor: Colors.white,
                    contentPadding: EdgeInsets.all(
                        MediaQuery.of(context).size.height * 0.02),
                    labelText: 'Epost',
                  ),
                  textCapitalization: TextCapitalization.none),
            ),
            Spacer(
              flex: 1,
            )
          ],
        ),
      ),
    );
  }

  Widget buildKeyBoardHiddenUsernameTextfield() {
    return Expanded(
      flex: 8,
      child: Container(
        child: Row(
          children: <Widget>[
            Spacer(
              flex: 1,
            ),
            Flexible(
              flex: 8,
              child: TextField(
                  focusNode: emailFocus,
                  controller: _usernameController,
                  autocorrect: false,
                  keyboardType: TextInputType.emailAddress,
                  maxLengthEnforcement: MaxLengthEnforcement.none,
                  onChanged: usernameChange,
                  style: TextStyle(
                      height: 1,
                      fontSize: displayWidth * globals.textFieldFontSize0),
                  decoration: InputDecoration(
                    border: OutlineInputBorder(
                        borderRadius: BorderRadius.circular(10.0)),
                    filled: true,
                    fillColor: Colors.white,
                    contentPadding: EdgeInsets.all(
                        MediaQuery.of(context).size.height * 0.02),
                    labelText: 'Epost',
                  ),
                  textCapitalization: TextCapitalization.none),
            ),
            Spacer(
              flex: 1,
            )
          ],
        ),
      ),
    );
  }

  Widget buildKeyboardVisiblePasswordTextfield() {
    return Flexible(
      flex: 18,
      child: Row(
        children: <Widget>[
          Spacer(
            flex: 1,
          ),
          Flexible(
            flex: 8,
            child: TextField(
                focusNode: passwordFocus,
                maxLengthEnforcement: MaxLengthEnforcement.none,
                controller: _passwordController,
                autocorrect: false,
                style: TextStyle(
                    height: 1,
                    fontSize: displayWidth * globals.textFieldFontSize0),
                decoration: InputDecoration(
                  contentPadding:
                      EdgeInsets.all(MediaQuery.of(context).size.height * 0.02),
                  border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0)),
                  filled: true,
                  fillColor: Colors.white,
                  labelText: 'Lösenord',
                ),
                obscureText: true,
                textCapitalization: TextCapitalization.none),
          ),
          Spacer(
            flex: 1,
          )
        ],
      ),
    );
  }

  Widget buildKeyboardHiddenPasswordTextfield() {
    return Flexible(
      flex: 8,
      child: Row(
        children: <Widget>[
          Spacer(
            flex: 1,
          ),
          Flexible(
            flex: 8,
            child: TextField(
                focusNode: passwordFocus,
                maxLengthEnforcement: MaxLengthEnforcement.none,
                controller: _passwordController,
                autocorrect: false,
                style: TextStyle(
                    height: 1,
                    fontSize: displayWidth * globals.textFieldFontSize0),
                decoration: InputDecoration(
                  contentPadding:
                      EdgeInsets.all(MediaQuery.of(context).size.height * 0.02),
                  border: OutlineInputBorder(
                      borderRadius: BorderRadius.circular(10.0)),
                  filled: true,
                  fillColor: Colors.white,
                  labelText: 'Lösenord',
                ),
                obscureText: true,
                textCapitalization: TextCapitalization.none),
          ),
          Spacer(
            flex: 1,
          )
        ],
      ),
    );
  }

  Widget buildKeyboardAdaptedResponseForms() {
    return Expanded(
      flex: isKeyboardVisible ? 6 : 5,
      child: Row(
        mainAxisAlignment: MainAxisAlignment.start,
        crossAxisAlignment: CrossAxisAlignment.center,
        mainAxisSize: MainAxisSize.max,
        children: <Widget>[
          Spacer(
            flex: 1,
          ),
          Expanded(
            flex: 8,
            child: Container(
                child: Text("$loginResponse",
                    overflow: TextOverflow.visible,
                    style: new TextStyle(
                        fontSize: displayWidth * globals.linkFontSize0,
                        color: Colors.red),
                    textAlign: TextAlign.left)),
          ),
          Spacer(
            flex: 1,
          )
        ],
      ),
    );
  }

  Widget buildKeyboardAdaptedRememberMeForms() {
    return isKeyboardVisible
        ? Container()
        : Expanded(
            flex: 3,
            child: Row(
              mainAxisSize: MainAxisSize.max,
              mainAxisAlignment: MainAxisAlignment.start,
              crossAxisAlignment: CrossAxisAlignment.center,
              children: <Widget>[
                Spacer(
                  flex: 1,
                ),
                Expanded(
                  flex: 8,
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.start,
                    crossAxisAlignment: CrossAxisAlignment.center,
                    mainAxisSize: MainAxisSize.max,
                    children: <Widget>[
                      Container(
                        child: Text(
                          "Kom ihåg mig",
                          overflow: TextOverflow.visible,
                          style: TextStyle(
                              fontSize: displayWidth * globals.linkFontSize0),
                        ),
                      ),
                      Container(
                        child: Checkbox(
                          value: rememberMe,
                          onChanged: rememberMeChange,
                        ),
                      )
                    ],
                  ),
                ),
                Spacer(
                  flex: 1,
                )
              ],
            ),
          );
  }

  Widget buildKeyboardAdaptedIForgotPasswordLink() {
    return isKeyboardVisible
        ? Container()
        : Flexible(
            flex: 5,
            child: Row(
              mainAxisSize: MainAxisSize.max,
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: <Widget>[
                Spacer(
                  flex: 1,
                ),
                Expanded(
                  flex: 8,
                  child: Container(
                    alignment: Alignment.centerLeft,
                    child: InkWell(
                      child: Text(
                        "Jag har glömt mitt lösenord",
                        textAlign: TextAlign.left,
                        style: TextStyle(
                            decoration: TextDecoration.underline,
                            fontSize: displayWidth * 0.04),
                      ),
                      onTap: resetPasswordPressed,
                    ),
                  ),
                ),
                Spacer(
                  flex: 1,
                )
              ],
            ),
          );
  }

  buildKeyboardAdaptedButtonBar() {
    return Flexible(
        flex: isKeyboardVisible ? 14 : 6,
        child: Row(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Spacer(
              flex: 4,
            ),
            Expanded(
                flex: 15,
                child: ElevatedButton(
                  style: ButtonStyle(
                      backgroundColor: MaterialStateColor.resolveWith(
                          (states) => Color.fromRGBO(217, 217, 217, 1))),
                  child: Text(
                    'Logga in',
                    textAlign: TextAlign.center,
                    style: new TextStyle(
                        fontSize: displayWidth * globals.buttonFontSize0,
                        color: new Color.fromRGBO(54, 104, 129, 1.0)),
                  ),
                  onPressed: loggaInPressed,
                )),
            Spacer(
              flex: 1,
            ),
            Expanded(
              flex: 15,
              child: ElevatedButton(
                style: ButtonStyle(
                    backgroundColor: MaterialStateColor.resolveWith(
                        (states) => Color.fromRGBO(217, 217, 217, 1))),
                child: Text(
                  'Avbryt',
                  style: new TextStyle(
                      fontSize: displayWidth * globals.buttonFontSize0,
                      color: new Color.fromRGBO(54, 104, 129, 1.0)),
                ),
                onPressed: () {
                  resetFocus();
                  _usernameController.clear();
                  _passwordController.clear();
                },
              ),
            ),
            Spacer(
              flex: 4,
            ),
          ],
        ));
  }

  void setDisplayDimensions() {
    if (displayWidth == 1) displayWidth = MediaQuery.of(context).size.width;
  }

  void onKeyboardVisibilityChange(bool visible) {
    setState(() {
      isKeyboardVisible = visible;
    });
  }

  hideSystemOverlay() {
    SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
  }

  void onScaffoldTap() {
    resetFocus();
  }

  void resetFocus() {
    try {
      if (emailFocus.hasFocus) emailFocus.unfocus();
      if (passwordFocus.hasFocus) passwordFocus.unfocus();
      if (passwordFocus.hasFocus || emailFocus.hasFocus)
        Focus.of(context).unfocus();
    } catch (e) {}
  }

  onSwipeDown() {
    resetFocus();
  }

  buildPolicyLink() {
    return InkWell(
      child: Text(
        "Sekretesspolicy",
        style: TextStyle(
            fontSize: displayWidth * globals.linkFontSize0,
            color: Colors.blue,
            decoration: TextDecoration.underline),
      ),
      onTap: privacyPolicyTapped,
    );
  }

  buildVersionNr() {
    return Text(
      "Version 1.2.9",
      style: TextStyle(
          fontSize: displayWidth * globals.linkFontSize0, color: Colors.black),
    );
  }

  buildControlsLayer() {
    return Center(
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          Spacer(flex: isKeyboardVisible ? 2 : 7),
          buildLogo(isKeyboardVisible ? 40 : 20),
          Spacer(flex: isKeyboardVisible ? 1 : 3),
          Flexible(
            flex: 10,
            child: Text(
              "Välkommen till My App",
              overflow: TextOverflow.visible,
              style: new TextStyle(
                  fontSize: displayWidth * globals.titleFontSIze0),
              textAlign: TextAlign.center,
            ),
          ),
          Spacer(
            flex: 7,
          ),
          buildUsernameTextfieldAdaptedToKeyboard(),
          Spacer(flex: isKeyboardVisible ? 4 : 2),
          buildPasswordTextfieldAdaptedToKeyboard(),
          isKeyboardVisible ? Spacer(flex: 2) : Container(),
          buildKeyboardAdaptedResponseForms(),
          buildKeyboardAdaptedRememberMeForms(),
          buildKeyboardAdaptedIForgotPasswordLink(),
          Spacer(flex: isKeyboardVisible ? 4 : 2),
          buildKeyboardAdaptedButtonBar(),
          Spacer(
            flex: isKeyboardVisible ? 2 : 10,
          )
        ],
      ),
    );
  }

  buildPolicyLayer() {
    return isKeyboardVisible
        ? Container()
        : Positioned(
            bottom: 10,
            left: 10,
            child: buildPolicyLink(),
          );
  }

  buildVersionLayer() {
    return isKeyboardVisible
        ? Container()
        : Positioned(bottom: 10, right: 10, child: buildVersionNr());
  }
}

pubspec.yaml:

name: myapp
description: A new Flutter project.

version: 1.0.0+1

environment:
  sdk: ">=2.12.0 <3.0.0"

dependencies:
  http: ^0.13.4
  flutter:
    sdk: flutter
  firebase_messaging: ^11.0.0
  intl: ^0.17.0
  shared_preferences: ^2.0.8
  path_provider_platform_interface: ^2.0.1
  platform: ^3.0.2
  flutter_secure_storage: ^4.2.1
  swipedetector: ^1.2.0

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.3
  url_launcher: ^6.0.3
  firebase_core: ^1.0.4
  flutter_keyboard_visibility: ^5.1.0

dev_dependencies:
  flutter_test:
    sdk: flutter

# For information on the generic Dart part of this file, see the
# following page: https://www.dartlang.org/tools/pub/pubspec

# The following section is specific to Flutter.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true
  assets:
    - assets/myapp_logo.png
    - assets/myapp_logo.png
    - assets/logo_small.png
    - assets/green_smiley.png
    - assets/orange_smiley.png
    - assets/red_smiley.png
    - assets/horizontal_line.png

最后的想法和问题

为什么在发布版本中登录页面没有正确呈现,但在调试模式下工作?

谢谢!

标签: flutterdart

解决方案


可能是因为渲染运行时出现错误(异常)。你检查过是否有异常吗?例如,如果您使用的是 Sentry 之类的错误报告工具,请转到其网页查看。如果您没有,请尝试查看日志。或者,按照官方指南设置错误处理(例如简单地打印它):https ://flutter.dev/docs/testing/errors 。

如果找不到任何线索,请尝试设置错误处理并将所有日志放在这里,我可以尝试查看。


编辑

有了更多信息,我可以解释发生了什么。

  void setDisplayDimensions() {
    if (displayWidth == 1) displayWidth = MediaQuery.of(context).size.width;
  }

  void buildOtherPartsOfUI() {
   ...use displayWidth...
  }

这似乎不是一个完美的方法。如果在 buildOtherPartsOfUI之后调用 setDisplayDimensions ,则 UI 将以旧宽度而不是新获取的宽度呈现。它只会在下次build发生时更新。然而,更糟糕的是,您无法控制下一次构建发生的时间。(您可以使用setState让 Flutter 知道它应该build在下一帧调用;但由于您不这样做,Flutter 可以build稍后调用,或者build如果 UI 不变,它可以在 100 年后调用。)


推荐阅读