flutter - 为什么我的 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
最后的想法和问题
为什么在发布版本中登录页面没有正确呈现,但在调试模式下工作?
谢谢!
解决方案
可能是因为渲染运行时出现错误(异常)。你检查过是否有异常吗?例如,如果您使用的是 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 年后调用。)
推荐阅读
- javascript - 将类分配给动态创建的 div
- javascript - gmail插件将邮件拖放到
- list - 合并排序不适用于升序
- php - 如何创建 URL 以使用 HTML 和 PHP 自动下载文件?
- c# - 无法将项目添加到 Collection 属性。请帮帮我,拜托
- visual-studio-code - cmd shell 中的 Ctrl+C 导致 VSCode 退出
- macos - 是否可以通过 Applescript 访问 Spotify Mac 应用程序中当前播放歌曲的歌曲名称?
- javascript - 如何通过 event.target 区分同一层中的 react konva 元素?
- c# - C# 不会运行 PowerShell 脚本
- c# - 为什么我的 Web API 没有响应我的请求?