Flutter: потоковая передача нулевого значения на Android Virtual Device, но работает как положено на реальном телефоне Android - PullRequest
0 голосов
/ 04 мая 2019

У меня очень странное поведение с StreamControllers в моем приложении флаттера на виртуальных устройствах Android с Android Studio. Когда я подключаю реальное устройство Android, приложение работает нормально.

Я использую блок для формы авторизации, которая прослушивает изменения, проверяет значения, а затем отправляет их на сервер.

Проблема, с которой я столкнулся, заключается в том, что и адрес электронной почты, и пароль правильно добавляются в поток и проходят проверку, как и ожидалось.

Однако, когда я нажимаю submit в submitForm (), если последнее поле, к которому я прикоснулся, это пароль, тогда значение _email.value равно нулю. Если последнее поле, к которому я прикоснулся, - это электронная почта, тогда значение _password.value равно нулю. Опять же, это происходит только в AVD. Когда я использую свой реальный телефон, значения и _email, и _paswsword ожидаются.

Я полностью удалил Android Studio и переустановил, но результат тот же. Я использую Hyper V и платформу Windows Hypervisor, поскольку использую докер, но от Android не было предупреждений о HAMX.

UPDATE:

Я только что установил genymotion, и он также работает нормально, так что, похоже, проблемы с AVD в Windows. Я все еще хотел бы заставить AVD работать, поскольку движение geny не работает на Hyper V.

Система:

OS Name Microsoft Windows 10 Pro
Version 10.0.17763 Build 17763
Other OS Description    Not Available
OS Manufacturer Microsoft Corporation
System Manufacturer MSI
System Model    MS-7A68
System Type x64-based PC
System SKU  Default string
Processor   Intel(R) Core(TM) i7-7700 CPU @ 3.60GHz, 3600 Mhz, 4 Core(s), 8 Logical Processor(s)
BIOS Version/Date   American Megatrends Inc. 1.30, 28/06/2017
SMBIOS Version  3.0
Embedded Controller Version 255.255
BIOS Mode   UEFI
BaseBoard Manufacturer  MSI
BaseBoard Product   Z270 TOMAHAWK (MS-7A68)
BaseBoard Version   1.0
Platform Role   Desktop
Secure Boot State   Off
PCR7 Configuration  Binding Not Possible
Windows Directory   C:\WINDOWS
System Directory    C:\WINDOWS\system32
Boot Device \Device\HarddiskVolume3
Locale  United States
Hardware Abstraction Layer  Version = "10.0.17763.404"
Time Zone   GMT Daylight Time
Installed Physical Memory (RAM) 16.0 GB
Total Physical Memory   16.0 GB
Available Physical Memory   4.49 GB
Total Virtual Memory    21.5 GB
Available Virtual Memory    5.44 GB
Page File Space 5.50 GB
Page File   C:\pagefile.sys
Kernel DMA Protection   Off
Virtualization-based security   Running
Virtualization-based security Required Security Properties  
Virtualization-based security Available Security Properties Base Virtualization Support, UEFI Code Readonly, Mode Based Execution Control
Virtualization-based security Services Configured   
Virtualization-based security Services Running  
Device Encryption Support   Reasons for failed automatic device encryption: TPM is not usable, PCR7 binding is not supported, Hardware Security Test Interface failed and device is not InstantGo, Un-allowed DMA capable bus/device(s) detected, TPM is not usable
A hypervisor has been detected. Features required for Hyper-V will not be displayed.    

Код:

блок:

import 'dart:async';
import 'dart:convert';
import 'package:rxdart/rxdart.dart';
import 'package:http/http.dart' as http;
import './auth_validator.dart';

class AuthBloc with AuthValidator {
  final _email = BehaviorSubject<String>();
  final _password = BehaviorSubject<String>();
  final _isLoading = BehaviorSubject<bool>();
  final _loginSucceded = BehaviorSubject<bool>();

  // stream getters
  Stream<String> get email => _email.stream.transform(validateEmail);
  Stream<String> get password => _password.stream.transform(validatePassword);
  Stream<bool> get isLoading => _isLoading.stream;
  Stream<bool> get loginSuccess => _loginSucceded.stream;
  Stream<Map> get redirect => Observable.combineLatest2(_loginSucceded,
      _isLoading, (log, load) => {'loginSuccess': log, 'isLoading': load});
  Stream<bool> get submitValid =>
      Observable.combineLatest2(email, password, (e, p) => true);

  // add data to sink onChange
  Function(String) get emailChanged => _email.sink.add;
  Function(String) get passwordChanged => _password.sink.add;

  login(jsonUser) async {
    try {
      // submit to server
      final http.Response response = await http.post(
        'http://192.168.1.213:5000/api/users/signin',
        body: jsonUser,
        headers: {'Content-Type': 'application/json'},
      );

      final Map<String, dynamic> decodedRes = await json.decode(response.body);

      if (response.statusCode == 200) {
        _loginSucceded.sink.add(true);
      }

      return decodedRes;
    } catch (e) {
      _isLoading.sink.add(false);
    }
  }

  void submitForm() async {
    try {
      final Map user = {
        'email': _email.value,
        'password': _password.value
      };
      final jsonUser = json.encode(user);
      print(jsonUser);

      await login(jsonUser);

      void dispose() {
        _email.close();
        _password.close();
        _isLoading.close();
        _loginSucceded.close();
      }
    } catch (e) {
      print('error: $e');
    }
  }
}

валидатор:

import 'dart:async';

class AuthValidator {
  final validateEmail = StreamTransformer<String, String>.fromHandlers(handleData: (email, sink) {
    if (!email.contains(RegExp(r"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?"))) {
      sink.addError('Please enter a valid email address');
    } else {
      sink.add(email);
    }
  });

  final validatePassword = StreamTransformer<String, String>.fromHandlers(handleData: (password, sink) {
      final goodLength = password.length >= 8;
      final hasNumber = password.contains(RegExp(r'[0-9]'));

      if (!goodLength) {
        sink.addError('Must be at least 8 characters and contains a number');
      } else {
        sink.add(password);
      }
    });
}

страница:

import 'package:flutter/material.dart';
import '../blocs/auth_bloc.dart';

class LoginPage extends StatelessWidget {
  final authBloc = AuthBloc();

  @override
  Widget build(BuildContext context) {
    // TODO redirect would not work in StreamBuilder block
    // manually listening for redirect conidtions here
    // once a response is received from the server
    authBloc.loginSuccess.listen((data) {
      if (data) {
        Navigator.pushReplacementNamed(context, '/dashboard');
      }
    });

    return StreamBuilder(
        stream: authBloc.isLoading,
        initialData: false,
        builder: (context, snapshot) {
          print(snapshot.data);
          return Scaffold(
            body: _buildPage(authBloc, snapshot.data),
          );
        });
  }

  Widget _buildPage(AuthBloc authBloc, bool isLoading) {
    return Container(
      margin: EdgeInsets.all(20.0),
      child: Center(
        child: SingleChildScrollView(
          child: Column(
            children: <Widget>[
              _emailField(authBloc),
              _padding(),
              _passwordField(authBloc),
              _padding(),
              _submitButton(authBloc, isLoading)
            ],
          ),
        ),
      ),
    );
  }

  Widget _circularSpinner() {
    return Center(
      child: CircularProgressIndicator(),
    );
  }

  Widget _emailField(AuthBloc authBloc) {
    return StreamBuilder(
      stream: authBloc.email,
      builder: (BuildContext context, snapshot) {
        return Container(
          padding: EdgeInsets.only(top: 10.0),
          child: TextField(
            onChanged: authBloc.emailChanged,
            autofocus: true,
            keyboardType: TextInputType.emailAddress,
            decoration: InputDecoration(
              hintText: 'you@example.com',
              labelText: 'Email Address',
              errorText: snapshot.error,
              border: OutlineInputBorder(),
            ),
          ),
        );
      },
    );
  }

  Widget _passwordField(AuthBloc authBloc) {
    return StreamBuilder(
      stream: authBloc.password,
      builder: (BuildContext context, snapshot) {
        return TextField(
          onChanged: authBloc.passwordChanged,
          obscureText: true,
          keyboardType: TextInputType.emailAddress,
          decoration: InputDecoration(
            hintText: '8 characters or more with at least 1 number',
            labelText: 'Password',
            errorText: snapshot.error,
            border: OutlineInputBorder(),
          ),
        );
      },
    );
  }

  Widget _padding() {
    return Padding(
      padding: EdgeInsets.only(top: 20.0),
    );
  }

  Widget _submitButton(AuthBloc authBloc, isLoading) {
    final spinner = CircularProgressIndicator();
    final button =  RaisedButton(
            child: Text('Login'),
            color: Colors.blue,
            onPressed: authBloc.submitForm,
          );
    return StreamBuilder(
        stream: authBloc.submitValid,
        builder: (context, snapshot) {
         return isLoading ? spinner : button;
        });
  }
}

...