-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
NEWS-32 Dodanie logowania i rejestracji firebasowej #28
Conversation
kightsonsanom
commented
Nov 21, 2020
lib/src/di/injector.dart
Outdated
@@ -53,20 +55,25 @@ void injectDependencies(Environment flavor) { | |||
_locator.registerSingleton<AppConfig>(AppConfig()); | |||
_locator.registerSingleton<AppInfoRepository>(AppInfoLocalRepository()); | |||
_locator.registerSingleton<PersistenceRepository>(SharedPreferencesPersistenceRepository()); | |||
_locator.registerSingleton<SettingsRepository>(SettingsLocalRepository(_locator<PersistenceRepository>())); | |||
_locator.registerSingleton<PresentationShowingRepository>(PresentationShowingLocalRepository(_locator<PersistenceRepository>())); | |||
_locator.registerSingleton<SettingsRepository>( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
większość zmian w tym pliku wynika z ctrl + alt + L, niestety z racji, że w robo mamy inny threshhold zawijania linii niż tu to ciężko mi to uspójnić 😞
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nie boli :) możesz powiedzieć co tam masz poustawiane to ustawię tak samo, żeby z czasem każdy pull request nie zawierał więcej zmian formatowania niż tych tych faktycznych :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/src/ui/pages/home/home_page.dart
Outdated
if (selectedPage == 0) { | ||
return Future.value(true); | ||
} else { | ||
setState(() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ten setState do wyjebania, bo notifier teraz zarządza stanem obecnej strony, żeby dało się cofać do tej samej strony po wejściu do settingsów
@override | ||
_SettingsPageState createState() => _SettingsPageState(); | ||
} | ||
|
||
class _SettingsPageState extends State<SettingsPage> { | ||
final SettingsNotifier _settingsNotifier = inject<SettingsNotifier>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zauważyłem, że łatwiej jest zarządzać stanem, kiedy każdy widok ma swojego notifiera inicjalizowanego w swojej klasie. Dzięki temu kiedy jakiś widok jest odpalany z kilku miejsc to nie trzeba się martwić o to czy jest notifier providowany przy nawigacji do takiego widoku + scope notifiera jest zawsze optymalny
lib/l10n/intl_en.arb
Outdated
"register": "Register", | ||
"registrationTitle": "Registration", | ||
"name": "Name", | ||
"email": "Email", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
E-mail
:)
import 'package:allthenews/src/domain/authorization/authorization.dart'; | ||
import 'package:firebase_auth/firebase_auth.dart'; | ||
|
||
class FirebaseAuthorization implements Authorization { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
W zasadzie to Authentication :) - może FirebaseAuthenticationRepository i odpowiednio AuthenticationRepository interfejs? Trochę inaczej popakietować można, bo teraz w communication jest pakiet api
i pakiet firebase
a w zasadzie oba to api 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
proponuję to co w api obecnie dać do pakietu nytimes
i wrzucić paczkę firebase
do api
import 'package:firebase_auth/firebase_auth.dart'; | ||
|
||
class FirebaseAuthorization implements Authorization { | ||
FirebaseAuth auth = FirebaseAuth.instance; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i final :D
FirebaseAuth auth = FirebaseAuth.instance; | ||
|
||
@override | ||
Future<UserCredential> createUser(String email, String password) async => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zbędne async, pozostałe metody to samo
|
||
abstract class Authorization { | ||
|
||
Stream<User> userStream(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
w takim klasycznym podejściu pole reprezentuje stan, a metoda zachowanie, więc jak userStream
to fajnie jakby było polem, jak metoda, to getUserStream
albo observeUserChanges
:)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
observeUserChanges mi się bardziej podoba :)
lib/src/main_common.dart
Outdated
injectDependencies(flavor); | ||
FlutterError.onError = Crashlytics.instance.recordFlutterError; | ||
WidgetsFlutterBinding.ensureInitialized(); | ||
await Firebase.initializeApp(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
await w mainie blokuje wątek więc trochę słabo - i widzę, że tak jest w example na pub.dev, ale w dokumentacji już dużo ładniej to ograli:
https://firebase.flutter.dev/docs/overview#initializing-flutterfire
Generalnie jest issue, że na flutterze 1.20 nie śmiga, ale fixem jest pozostawienie WidgetsFlutterBinding.ensureInitialized()
w mainie :)
Tu wspomniane issue: firebase/flutterfire#3490
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
miałem to na liście rzeczy do poprawienia, ale cocoapods na iosie się zjebał i potem już zapomniałem :p
import 'package:flutter/material.dart'; | ||
|
||
mixin ErrorMessage { | ||
Widget buildErrorMessage(FirebaseAuthException error) => error != null |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jako, że jest w pakiecie common to powinien być bardziej generyczny tj. przyjmować Exception
, chyba, że ma być typowo pod FirebaseAuthException, wówczas chyba powinien być gdzieś w pakiecie authentication
i ewentualnie mógłby nazywać się AuthenticationErrorMessage
i odpowiednio buildAuthenticationErrorMessage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to błąd, który będzie raczej używany tylko przy autentykacji, więc wybiorę tą drugą opcję
}); | ||
|
||
@override | ||
Widget build(BuildContext context) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Zastanawiam się czy podejście jest poprawne, bo skoro ChangeNotifier służy do zarządzania stanem to wydaje mi się, ze powinno być tak, że kiedy np. wpisuje się coś do pola password, to to triggeruje metodę notifier.updatePassword(password)
, co zmienia wewnatrz notifiera stan (state.password
), który potem z powrotem propagowany jest na UI do TextFormField i analogicznie z walidacją, tj. po wciśnięciu przycisku wykonywana jest walidacja w notifierze, który w przypadku pustych pól wrzuca do viewState błąd Pole nie może być puste
, który dodawany jest do TextFormFielda
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zauważ, że użyłem tu podejścia z widgetem Form
- https://flutter.dev/docs/cookbook/forms/validation
to on przechowuje stan walidacji pod spodem i używa to tego klucza
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
notifier posiada tylko metodę to walidacji pól, ale postanowiłem lokalnie walidować tylko niepustość i dlatego każde pole ma taką samą metodę walidującą. Podejście o którym mówisz sprawdziłoby się gdyby przechowywanie stanu walidacji było całkowicie po naszej stronie
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
znaczy ja rozumiem to podejście, tylko chodzi mi o to, że jeżeli mamy architekturę, w której notifier propaguje stan na ui to powinien propagować wszystko, bo teraz część stanu jest zarządzana przez notifiera, a część przez sam ui 🤔 Dokumentacja flutterowa nie uwzględnia żadnej architektury. W necie za bardzo nie ma użycia providera z formularzem i jego walidacją (znalazłem jeden ale gość pomieszał bloc z providerem :P), ale chodzi mi o mniej więcej coś takiego jak w samplach flutter_bloc:
https://github.com/felangel/bloc/blob/master/examples/flutter_form_validation/lib/main.dart
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
w tym przykładzie co pokazałeś w ogóle nie jest używany widget Form
, który ogarnia stany TextFormFieldów
OOTB. Tak naprawdę jedyny powód, dla którego są tam TextFormFieldy
to atrybut initialValue
(który można by było też ograć na zwykłym TextFieldzie
). Moim zdaniem jeżeli jest jakaś funkcjonalność dostępna przez framework to warto z niej skorzystać, jeżeli chcielibyśmy wszystko trzymać w stanie to równie dobrze pozycje scrolla moglibyśmy tam przechowywać, tylko po co?
Ja sugerowałem się przykładem z exampli do flutter_auth -> https://github.com/FirebaseExtended/flutterfire/blob/799231ac57a3005a4fbc8fd9c5951c3943e4a286/packages/firebase_auth/firebase_auth/example/lib/register_page.dart
W przypadku gdyby walidacja po stronie mobilnej byłaby bardziej skomplikowana i doszłoby do sytuacji gdzie trzeba customizować ten stan z Forma
to wtedy opłacałoby się zachowywać ten stan ręcznie, ale w tym przypadku myślę, że skorzystanie z OOTB zachowania stanu ma sens
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ogólnie stan we flutterze można cały trzymać na ui i są wszystkie niezbędne narzędzia do tego, żeby zarządzać stanem tak jak to zostało zaprojektowane. Nie wymusza żadnej architektury. Jako, że nie wymusza architektury to powstało x różnych, które służą zarządzaniem stanu. Ten przykład z flutterfire nie ma żadnej architektury. Stan można ograć na UI, ale mimo wszystko stworzyłeś HomeNotifier (i słusznie), który wyciąga stan do notifiera, gdzie setState też to ogarnia, ale z notifierem jest zdecydowanie czyściej. Jeżeli przyjęta architektura wyciąga stan do notifierów i propaguje ten stan na ui to powinno być to konsekwetne. Jeżeli trzeba by było zarządzać samemu stanem scrolla to imo również powinien być propagowany z notifiera, ale na szczęście nie trzeba. Natomiast stan pól powinien być przechowywany w notifierze, ponieważ jest częścią stanu UI, który musi być zarządzany - tj. walidowany i wysyłany na backend. Sama walidacja też powinna odbywać się w walidatorze, który byłby gdzieś w domenie i notifier by sobie nim walidował pola przy kliknięciu buttona, a następnie wrzucił do viewState informację czy ma wyświetlić błedy walidacyjne i jak tak to które. W tym podejściu nie ma znaczenia czy walidacja jest skomplikowana czy nie, bo nawet jakby się skomplikowała to dla widgetów jest to niezauważalne, przeływ danych jest identyczny :) Kolejna kwestia to, że trzymanie wszystkiego w jednym miejscu (notifierze) pozwoli przetestować logikę klasę w izolacji od UI :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
trochę nie widzę tej granicy kiedy korzystać z narzędzi ogarniających stan OOTB, a kiedy już samemu tym zarządzać. W tym przypadku widgety Form
i TextFormField
stają się bezużyteczne. Jeżeli chcemy mieć stan w notifierze + korzystać z tego co oferuje framework to może trzymać w stanie notifiera sam GlobalKey
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Jeżeli jedyną wartością Form i TextFormField jest zarządzanie stanem na ui to przy obecnej architekturze rzeczywiście są bezużyteczne, ale nie powiedziałbym, że to coś złego. Wiesz, w androidzie jak robisz MVP to nie pchasz LiveDaty na UI tylko dlatego, że jest coś takiego, tylko trzymasz się założeń. Różnica między tym co mamy a blocem jest taka, że tam wszystko opiera się na strumieniach i powiedzmy, że jest większe wyróżnienie istoty eventów propagowanych z ui do bloca. I wg sampli też nie korzysta z walidacji, którą daje Form i to też nic złego. Takie podejście fajnie się sprawdza na pewno przy Simple app state management
(na zakładce List of state management approaches
na flutter.dev), ale założenia, które przyjęliśmy przy providerze są jednak inne. Imo nie ma co mieszać dwóch podejść i ładnie zrobić stan samemu, bo wpychanie GlobalState, który rozszerza State<StatefulWidget>>
to trochę jednak wpychanie UI wyżej (do notifiera) niż powinien być (UI)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, przekonałeś mnie, zrobię zarządzanie stanem walidacji z poziomu notifiera 😄
FlatButton( | ||
onPressed: () => _navigateTo(LoginPage()), | ||
child: Text( | ||
Strings.of(context).login, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
konsekwentnie moglibyśmy zawsze używać Strings.current
:)
void _updateUserName(User user, String name) { | ||
authorization | ||
.updateUser(name) | ||
.then((_) => returnToProfile.call()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
returnToProfile?.call()
i w pozostałych miejscach też z ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no właśnie zastanawiałem się czy to konieczne, dałem z ?
przy logoucie bo to jest na ekranie profilu i z tamtego miejsca nie przekazujemy callbacku do cofania. Pozostałe dwa miejsca przy metodach 'signIni
_updateUserName` będą wywołane tylko po wejściu do erkanu logowania/rejestracji, więc ten callback nie powinien być tam nullem 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a poza tym to myślałem czy warto na tym etapie przerzucać się na navigation 2.0, co myślisz?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
powiedzmy, że z poziomu samego notifiera widzisz tylko, że callback może być zainicjalizowany ale nie musi, bo nie jest to realizowane przez konstruktor, więc w przypadku kiedy zawsze będzie ?
nie ma potrzeby zastanawiania się czy ma prawo wystąpić czy nie, bo w zależności od użycia może ;) Co do navigation 2.0 to jak najbardziej :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ma to sens, dodam ?
. Dodałem też taska na trello do navigatora :)
import 'package:flutter/widgets.dart'; | ||
|
||
class AuthorizationNotifier extends ChangeNotifier { | ||
final Authorization authorization = FirebaseAuthorization(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private + wstrzykiwanie do konstruktora :)
|
||
VoidCallback returnToProfile; | ||
|
||
void getCurrentUser() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dziwnie wygląda metoda wyglądająca jak getter, która nic nie zwraca ;)
final viewState = | ||
providerContext.select((AuthorizationNotifier notifier) => notifier.state); | ||
|
||
if (viewState.user == null) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
można by było zrobić jakoś bardziej generycznie bo jak domniemam, każdy ekran, który będziemy mieli w profilu będzie musiał sprawdzać czy użytkownik jest zalogowany? Więc może jakiś abstrakcyjny notifier i page dla tych ekranów? :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
co masz na myśli pisząc "każdy ekran w profilu"? zakładałem, że to będzie tylko jeden widok. Tak czy inaczej rzeczy związane z profilem zostawiłbym na taska z profilem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
chodzi mi o to, że z poziomu profilu będziemy przechodzić na inne zakładki i one też będą autoryzowane, bo w teorii może się zdarzyć, że ktoś zostanie na tym ekranie, apkę zrzuci na stos, potem wróci i już nie będzie zalogowany? Może być osobno przy pierwszym ekranie profilowym, jak najbardziej :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
może się tak zdarzyć jakby usunąć usera ręcznie z firebase i chyba jakby się ktoś w międzyczasie na innym device zalogował, ale nie sprawdzałem tego w sumie
children: [ | ||
Text(_authorizationNotifier.state.user?.email), | ||
Text(_authorizationNotifier.state.user?.displayName), | ||
Text("User uid: ${_authorizationNotifier.state.user?.uid}"), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do stringsów :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docelowo myślę, że nie powinniśmy tej informacji wyświetlać na UI, dałem bardziej na potrzeby debugowania, proponuję, że dodam TODOsa na usunięcie tego przy okazji implementacji widoku profilu?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zawsze można ograć ładnie, tak żeby wyświetlać tylko w devowej wersji, tj. notifier miałby informację czy wyświetlać czy nie na podstawie Environment :) Chyba, że faktycznie do wywalenia, to może być todos :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
raczej mało przydatne info, mail jest i tak unikatowy, w sumie to już teraz wywalę
|
||
Stream<User> userStream(); | ||
|
||
Future<UserCredential> createUser(String email, String password); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
domena powinna być czysto dartowa, więc mimo wszystko zbudowałbym swoje obiekty domenowe zamiast korzystania z tych firebasowych, żeby ich zasięg kończył się na api, a nie przechodził przez domenę aż do ui
import 'package:firebase_auth/firebase_auth.dart'; | ||
import 'package:flutter/widgets.dart'; | ||
|
||
class AuthorizationNotifier extends ChangeNotifier { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
imo osobny notifier dla profilu, rejestracji i logowania, profil korzysta tylko z userStream, rejestracja z createUser, a logowanie z signIn
A no i wszystkie komunikaty niezależnie od języka przychodzą po angielsku, więc musiałoby być u nas mapowanie błędów firebasowych na nasze komunikaty |
jak jestem zalogowany to przy przechodzeniu z dashboardu na profil ekran miga - na początku jest ekran logowania potem dopiero profil, może dałoby radę ograć to jakoś loadingiem, bo jednak niefajnie wygląda to mignięcie |
border: OutlineInputBorder( | ||
borderRadius: BorderRadius.circular(_Constants.borderRadius), | ||
), | ||
hintStyle: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hint mógłby być innego koloru niż tekst
controller: textController, | ||
obscureText: obscureText, | ||
decoration: InputDecoration( | ||
labelText: hint, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jakoś dziwnie wygląda jak do labelText jest przypisywany hint, trudno mi to wytlumaczyć, ale jakby było odwrotnie to jakoś mniej dziwnie by mi to wyglądało, może dlatego, ze każdy label pasuje jako hint ale nie każdy hint pasuje jako label 🤔 - nie wiem czy słusznie, teraz przyszło mi do głowy :D
notifyListeners(); | ||
} | ||
|
||
void _onUserAuthFailed(exception) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
brakuje typu dla exception
child: Column( | ||
mainAxisAlignment: MainAxisAlignment.center, | ||
children: [ | ||
AuthTextFormField( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pole email mogłoby mieć TextInputType.emailAddress
:)
osobno wyciągnąłbym pakiety authentication i registration poza profile, bo jednak są to osobne domeny |
} | ||
|
||
Future _navigateTo(Widget page) { | ||
return Navigator.pushReplacement( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ogólnie to ten return potrzebny? 🤔
_updateUserName(name); | ||
} on Exception catch (exception) { | ||
_setNotifierState(_state.copyWith( | ||
exception: _exceptionMapper.toDomainException(exception), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mapowanie powinno odbyć się w warstwie data, gdzieś bliżej komunikacji, w tym przypadku w firebaseAuthenticationRepository
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
właśnie moim zdaniem powinny być mapowane jak najbliżej warstwy prezentacji, żeby nie przepychać tych błędów przez wszystkie warstwy, ale tak jak napisałem we wcześniejszym komentarzu
Co do exceptionów to myślę, że dobrze byłoby ten temat przegadać i uspójnić, celowo dałem to domeny, bo w ten sposób przy wielu warstwach tylko w jednym miejscu trzeba łapać wyjątek, a nie w każdej warstwie po kolei go przepychać,
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generalnie exceptiony są domeną. W warstwie prezentacji powinien być jakiś mapper, który na podstawie błędów domenowych wyświetla odpowiedni komunikat. Tak jak zwrotka w postaci sukcesu jest mapowana na obiekt domenowy np. Article, tak też wszystkie błędy powinny być mapowane w warstwie danych tak, aby warstwa domeny nie miała żadnych zależności komunikacyjnych, ani żeby błędy komunikacyjne nie przebijały się do warstwy prezentacji. Data korzysta z domeny, ui korzysta z domeny i to tyle z zależności między warstwami. Wiesz, skoro jsona mapujemy na domenę to czemu nie error? W przypadku przepychania błędów z api wprost do ui, domena i ui przechowują te błędy. I przykładowo dostaniemy DioError, czemu w ogóle UI ma wiedzieć, że korzystamy z Dio jako klienta http, albo z firebase? Ucinanie tego poprzez mapowanie w data powoduje brak konieczności wprowadzania zmian w pozostałych warstwach.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
czyli wychodzi na to, że jedynym miejscem gdzie nie będzie mapowań jest właśnie domena. Tak to w data będzie mapowania błędów api/firebase czy tam cokolwiek innego na domenowe błędy, a w warstwie ui domenowe błędy na stringsy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tak :) Analogicznie mapujemy teraz jeżeli chodzi o Article, w data z jsona na domenę, a w presentation z domeny na jakieś viewEntity w viewState :)
import 'package:flutter/cupertino.dart'; | ||
|
||
class RegistrationNotifier extends ChangeNotifier { | ||
final AuthenticationRepository _authorizationRepository; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_authenticationRepository
VoidCallback returnToProfile; | ||
|
||
Future<void> createUser(String email, String password, String name) async { | ||
_validateFields(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Średnio mi się podoba bo ta walidacja częściowo jest w tej metodzie a częściowo w tym kolejnym ifie. Może jakby sygnatura wyglądała mniej więcej tak to zwięźlej by to wyglądało? 🤔 Taki luźny pomysł
_validateFields(
onInvalid: (validationErrors) => ...,
onValid: (state) => ...
)
_setNotifierState(_state.copyWith(email: email)); | ||
} | ||
|
||
void setName(String name) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mega drobnostka ale może zamiast setName itd. to updateName (jakoś bardziej mi się kojarzy z aktualizacją stanu, ale może to moje jakieś takie spaczenie)
|
||
class LoginState { | ||
final bool isLoading; | ||
final AllTheNewsException exception; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthenticationException :)
lib/src/ui/pages/home/home_page.dart
Outdated
class HomePage extends StatefulWidget { | ||
final int initialPage; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
w sumie HomePage nie musi trzymać initialPage, może to robić notifier co nie? 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
to w ogóle można wyjebać
_setNotifierState(ProfileState(exception: exception as AuthenticationException)); | ||
} | ||
|
||
String checkIfEmpty(String text) => text.isEmpty ? Strings.current.emptyFieldError : null; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nieużywane
} | ||
|
||
extension on ChangeNotifier { | ||
void verifyCreateUser( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jak nie będziesz niepotrzebnie przesyłał parametrów do metod create i signIn to nie będą potrzebne te extensiony :)
Co do tych za dużych tasków to racja, tutaj powiedzmy spokojnie można było wydzielić na szybko osobno logowanie, rejestrację i update firebase, ale powiedzmy, że nawet w obrębie jednej funkcjonalności (np. logowanie) można na trello do taska porobić checkboxy jako subtaski i wrzucać je osobno, po kolei, jak już ten, który weźmie taska sobie rozplanuje co i w jakiej kolejności chce zrobić :) |
@@ -0,0 +1,23 @@ | |||
import 'package:allthenews/src/domain/communication/all_the_news_exception.dart'; | |||
|
|||
class AuthenticationApiException extends AllTheNewsException {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
powinno być w pakiecie lib/domain/authentication
, bo domeną jest autentykacja
import 'package:allthenews/src/domain/model/user.dart' as domain; | ||
import 'package:firebase_auth/firebase_auth.dart'; | ||
|
||
class FirebaseAuthenticationRepository implements AuthenticationRepository { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
zmieniłbym pakiet na lib/src/data/authentication
@@ -0,0 +1,13 @@ | |||
import 'package:allthenews/src/domain/model/user.dart'; | |||
|
|||
abstract class AuthenticationRepository { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
pakiet z lib/src/domain/authorization
na lib/src/domain/authentication
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do tej pory w authorization były rzeczy związane z kluczem api, jest to inny rodzaj autoryzacji niż logowanie od apki i dobrze by było to rozdzielić. Może obecne authorization
zmienię na apiauthentication
i zrobię osobny pakiet authentication
na rzeczy związane z logowaniem/rejestracją?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Te rzeczy co były wcześniej w authorization niech zostaną w authorization, niech powstanie osobny pakiet authentication i do niego wrzuć te rzeczy z logowaniem, bo ten api key to autoryzacja, a logowanie to autentykacja
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
git, też o tym myślałem, ale zbyt podobne wydawały mi się te nazwy
@@ -0,0 +1 @@ | |||
abstract class AllTheNewsException implements Exception {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dałbym w jakimś pakiecie lib/src/domain/common
bo exceptiony w aplikacji niekoniecznie będą zawsze związane z jakąś komunikacją
@@ -0,0 +1,3 @@ | |||
abstract class MessageProvider { | |||
String getMessage(Object error); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Klasa mogłaby się nazywać ErrorMessageProvider
skoro przyjmuje jako argument metody error
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nie jest powiedziane, że tylko errory będziemy mapować na stringi, z takiego samego powodu AllTheNewsException
dajemy do common. Za to nazwa parametru nie powinna się nazywać error
, tylko jakiś identifier
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
w takim razie przyznam, że średnio mi się podoba przepychanie Object jako parametr, już lepiej jakby był generykiem:
MessageProvider<T> { String getMessage(T object) }
@@ -0,0 +1,23 @@ | |||
import 'package:allthenews/src/domain/communication/all_the_news_exception.dart'; | |||
|
|||
class AuthenticationApiException extends AllTheNewsException {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
lib/src/domain/authentication
|
||
void validateFieldsAndSignIn() { | ||
_validateFields( | ||
onInvalid: () { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
onInvalid: () => notifyListeners()
import 'package:flutter/cupertino.dart'; | ||
|
||
class LoginNotifier extends ChangeNotifier { | ||
final AuthenticationRepository _authorizationRepository; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_authenticationRepository
import 'package:allthenews/src/domain/authorization/authentication_field_error.dart'; | ||
import 'package:allthenews/src/ui/common/message_provider.dart'; | ||
|
||
class FieldErrorMessageProvider extends MessageProvider { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
AuthFieldErrorMessageProvider
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nie jest powiedziane, że tylko błędy autentykacji tu będą, obecnie tak jest, ale jak dojdzie walidacja innych pól to tu też będzie można je określić. Myślę, że nie warto tego rozdzielać, bo te błędy mogą się powielać (np pustość). Taki był zamysł i zmieniłbym nazwę AuthenticationFieldError
na FieldError
void initUserState() { | ||
_setNotifierState(_state.copyWithLoading(isLoading: true)); | ||
_authorizationRepository.observeUserChanges().listen((user) { | ||
_setNotifierState(_state.copyWithUserAndLoading(user: user, isLoading: false)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
w sumie może tu wystąpić błąd, i zastanawiam się czy stream może nic nie wysłać, wtedy też będzie wieczny loading
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jeżeli nie będzie usera to zwróci nulla, każda inna zmiana będzie userem. Jeżeli chodzi o błędy to dokumentacja nic nie mówi na ten temat
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a choćby brak neta?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm sprawdzę
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
jak nie ma neta to user leci jako null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ciekawe :D w takim razie jest git :D
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
w sumie słabo, bo jak rozpoznać czy jestem niezalogowany czy po prostu nie mam neta? dopiero przy logowaniu się dowiem
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... racja, zostawiłbym to na osobne zadanie, bo chyba musi być na tym ekranie jakiś dodatkowy check robiony 🤔
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dodałem do trello
}); | ||
} | ||
|
||
Widget _bindLoading() => const CircularProgressIndicator(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
opakować w Center, żeby było na środku :)
@@ -0,0 +1,53 @@ | |||
import 'package:allthenews/src/domain/authorization/authentication_repository.dart'; | |||
import 'package:allthenews/src/ui/common/message_provider.dart'; | |||
import 'package:allthenews/src/ui/pages/authentication/authentication_message_provider.dart'; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nieużywany import
wszystko spoko, tylko powód tego faila na bitrise, ale to jak już poprawisz to merguj :) |