问题
我有一个Flutter应用程序,它有一个登录页面。当我在调试模式下运行应用程序时,当应用程序打开时,登录页面会正确呈现。但是当我用flutter build apk --release
构建应用程序的apk版本,安装它,然后在模拟器中打开应用程序时,登录页面不会正确呈现。请参见下面的截图。
在调试模式下运行,正确呈现:
运行发布版本,未完全呈现:
代码
登录页面代码:
// @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());
}
}
公共质量标准名称:
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
最后的想法和问题
为什么登录页面在发布版本中没有正确呈现,而是在调试模式下工作?
谢谢!
5条答案
按热度按时间qfe3c7zg1#
这可能是因为渲染运行时出现了错误(异常),你检查过是否有异常吗?例如,如果你正在使用Sentry这样的错误报告工具,请到它的网页上查看。如果你没有,请尝试查看日志。或者,按照官方指南设置错误处理(例如,简单地打印它):https://flutter.dev/docs/testing/errors.
如果你找不到任何线索,尝试设置错误处理,并把所有的日志在这里,我可以尝试看到它。
有了更多的信息,我可以解释发生了什么。
这似乎不是一个完美的方法。如果setDisplayDimensions在buildOtherPartsOfUI之后被调用,UI将使用旧的宽度而不是新获取的宽度呈现。它将只在下一个
build
发生时更新。然而,更糟糕的是,您无法控制下一个构建发生的时间。(您可以使用setState
让Flutter知道它应该在下一帧中调用build
;但由于您没有这样做,Flutter可以在以后调用build
,或者如果UI没有更改,它可以在100年后调用build
。)wrrgggsh2#
我也有一个类似的实现,就是尽早获取屏幕显示信息,计算比率并将其存储到一个静态字段中,这个静态字段用于让应用对设备的显示做出响应。
它在调试模式下工作正常,但在发布模式下无法正确呈现内容。
截图如下:
| 调试模式|发布模式|
| - ------|- ------|
|
|
|
根本原因
在发布模式下,应用不需要加载所有调试功能,因此轻量级应用的加载速度更快。
在Flutter 2.10.3中,它无法在构建应用程序的早期获得正确的显示信息,而且如果该值被存储并在以后重用,它将无法按预期工作。
解决方案(有点古怪......)
我在
runApp()
之前添加了一个延迟任务,就像我们在Flutter应用程序中初始化Firebase一样,这将为Flutter引擎完成设置预留足够的时间。这也解决了我的另一个问题。启动画面只显示在一个 Flink 在释放模式,我希望用户可以看到正确的标志。
启动应用程序的默认主函数:
解决方案-在
runApp()
之前添加延迟任务:具体实施请参考this repository。
ttcibm8c3#
发现问题
现在我发现了错误。问题是我如何使用
MediaQuery.of(context).size.width
。我首先调用setDisplayWidth()
方法来获得显示宽度,然后将该值赋给dispayWidth
变量。但由于某种原因,这在发布模式下不起作用。然而,由于setDisplayWidth()
调用没有按我的预期工作,因此小部件实际上被呈现了。所有文本的大小都设置为0 - 1像素,这使得文本/小部件看起来没有正确呈现。因为您可以在问题中的代码中看到我是如何相对于displayWidth
变量设置所有文本大小的。但是displayWidth
变量的默认值始终为1,这使得文本实际上不可见。为了解决上述问题,我完全删除了
displayWidth
变量,而是在每次需要显示宽度时直接在build方法中获取MediaQuery.of(context).size.width
。新代码(解决方案)
结论
在你的应用程序和小工具中使用
MediaQuery.of(context).size.width
时要小心。总是测试发布版本,因为它的行为可能与调试版本完全不同。我不知道为什么我的第一个代码不能正常工作。um6iljoc4#
我知道这个问题已经回答了,但对我来说,问题是灵活的组件,我用错了,如果其他答案对你没有帮助,看看这个组件文档,并检查它是否适合你的项目。
PS:在调试模式下我没有问题,只有在发布模式下。
dbf7pr2w5#
我遇到了这个问题。小部件在调试模式下工作正常,但在发布模式下没有显示。在我的情况下,问题已经通过修复
Incorrect use of ParentDataWidget
警告解决了。