Android Programjaim

Android, Flutter/Dart, Kotlin, Java, Unity, HarmonyOS

Android nepszerubb architecture-ok mostansag

2020. január 08. 09:16 - lacas8282

Android nepszerubb architecture-ok mostansag egy facebookos szavazast kovetoen (magyar csoport) :) droid_architect.png

komment

Flutter, dart es BloC architecture

2020. január 02. 13:10 - lacas8282

A Bloc/FlutterBloc-rol lesz szo a cikkben. https://github.com/felangel/bloc

Tehat ez is egy nepszerubb state management konyvtar, mint a MobX, csak maskepp mukodik. A mostani kodjaimat pl igy irom.

A lenyege a libnek, hogy megprobalja elkuloniteni az uzleti logikat a prezentacios retegtol.
Miert jo ez nekunk? A kod tisztabb lesz, szebb, amellett tesztelhetobb, ujrafelhasznalhatobb, attekinthetobb.

Egy par examplet latunk Felix Angelov repojaban is, ha akarjuk ezt is kovethetjuk alapkent. Bar en mar talalkoztam olyan sample koddal a repobol, ami nem mukodott.

Altalaban azt kell mondani, hogy nagyon hasonlo ez a cucc, mint a React Native-os REDUX. Persze en nem szeretem sajnos az RN-t, meg Reduxot se nagyon. Anno volt par honap szenvedesem vele.

Reakt Nativ sztrimeket (bocs a magyarert, dehat mit irjak? FOLYAMOT? azis gaz:/) hasznal ez a lib. Nezzunk rogton egy konkret peldat. (aki akar olvasgathat angolul meg RX-rol: https://www.didierboelens.com/2018/08/reactive-programming---streams---bloc/)

Pubspec.yaml-be betoljuk a

flutter_bloc: ^3.0.0

-ot a dependencies-ek koze, majd GET

Hogyha return 0  van, akkor eljenzes, minden sikerult, nem akadt ossze semmi semmivel.

A main dart file-ba a main method-ba kell egy

void main() async {

   ...

 BlocSupervisor.delegate = SimpleBlocDelegate();


Ez siman egy listener igazabol, ami a SimpleBlocDelegate-be kerul:

import 'package:bloc/bloc.dart';

class SimpleBlocDelegate extends BlocDelegate {
@override
void onEvent(Bloc bloc, Object event) {
super.onEvent(bloc, event);
print('bloc: ${bloc.runtimeType}, event: $event');
}

@override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print('bloc: ${bloc.runtimeType}, transition: $transition');
}

@override
void onError(Bloc bloc, Object error, StackTrace stacktrace) {
super.onError(bloc, error, stacktrace);
print('bloc: ${bloc.runtimeType}, error: $error');
}
}

Ez mondjuk legyen egy bloc_delegate.dart file-ba, es a main-bol importoljuk be...

Mint lathato lesz a kesobbiekben ha tortenik valami (Egy esemenyt atadunk a bloc-unknak, majd egy state-et visszaadunk), akkor ez a kis csoda loggolni fogja LogCat-re/vagy Run terminalra.

Ez arra szolgal, amire kitalaltak: Loggolasra. Hogy lasd mikor mi tortenik a hatterben...

main.dart-unk tehat ezt tartalmazza:

void main() async {
WidgetsFlutterBinding.ensureInitialized();
BlocSupervisor.delegate = SimpleBlocDelegate();

await SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
]).then((_) {
runApp(MyApp());

});
}

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: '...',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: BlocProvider<CounterBloc>(
create: (context) => CounterBloc(),
child: CounterPage(),
),
);
}
}

runApp(MyApp());


lefuttatja az elso gyoker widgetunket, ami a MyApp class.
Ez egy StateLess Widget ugye, nincsenek benne allapotok, valtozok.

home: BlocProvider

itt tortenik a varazslat. Egy providerrel megmondjuk, hogy melyik Bloc osztalyt fogjuk hasznalni melyik Page-nel.
Jo dolog, ha az elnevezesek kb tok ugyanazok, a kesobbiekben majd meglatjuk miert.

Csinalnunk kell counter.dart filet amiben a CounterPage lesz, es egy counter_bloc.dart -ot, amiben meg nyilvan a Bloc-unk.

A kodunk nem alap dolgokat fog hasznalni, mint abban amit Felix hasznal, de annyira nem is bonyolultat, hogy ne ertsuk mirol van szo. Alapbol a counteres peldanal maradunk, de lesz incremet/decrement event-unk (metodusunk), es lesz egy state-unk amit visszakapunk.

counter_bloc.dart

import 'package:bloc/bloc.dart';

class CounterBloc extends Bloc<CounterEvent, CounterState> {
@override
CounterState get initialState => CounterState(counter: 0);

@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
switch (event) {
case CounterEvent.decrement:
//await Future<void>.delayed(Duration(seconds: 1));
yield CounterState(counter: state.counter - 1);
break;
case CounterEvent.increment:
//await Future<void>.delayed(Duration(milliseconds: 500));
yield CounterState(counter: state.counter + 1);
break;
default:
throw Exception('unhandled event: $event');
}
}
}

 

Alapbol minden bloc felepitese hasonlo, extendalja a Bloc class-t, ami generikus cucc, van egy Event, State parosa. Annyi, hogy ez kb 'barmi' lehet, celszeru mar eleinte is class-okat rakni ra, State es Event elnevezesekkel, hogy tudjuk mit csinalunk.

Mit csinal a Bloc kb? Van egy magikus dobozod, amibe beledobsz egy eventet (mit akarsz csinalni) es ami ebbol kikop egy State-et (allapotot, valtozot, eredmenyt), amit megkapunk UI oldalon, igy nem kell szopni a setState-el es azzal, hogy mindig minden ujrabuildelodik/rajzolodik egy state change-kor.
Mindig van egy initialState ugye, ami az elso allapota a bemenetunknek, igy itt egy 0. (counter valtozo = 0)
Lesz egy CounterEvent es CounterState osztalyunk. Jelen esetben a CounterState egy enum, de en inkabb class-okat ajanlok erre is kesobb. Foleg ha sok Event es State lesz majd egy Bloc-hoz, akkor ajanlatos lesz egy BaseEvent es BaseState class, amibol oroklodik minden mas...

 Stream<CounterState> mapEventToState(CounterEvent event) async* {

 

Ez mi a franc? Kerdezhetnenk.

Pont az, amit magyarul ha leforditanank, ugyanaz tortenik. Mappeli a beerkezo Event-et egy kimeno State-e, miutan csinaltunk valamint a bemenettel. Mi az az async*? Hat az asyncrol mar biztos hallottal, Stream-eknel mikor yield-el adunk vissza erteket es nem return-al szokas hasznalni.
A peldakodbol kikommenteltem az await Future-okot, amik itt kb csak a webes tartalom eleresenek idejet korlatoznak, ha lenne.. (szemleltetnek egy kesest, amikor pl fetch-elunk egy adatot szerverrol)
Tehat varakozas siman.

CounterState ezek utan a counter valtozot vagy noveli vagy csokkenti es egy osztalyban visszaadja az UI-nak.

Nezzuk az UI-t:

class CounterPage extends StatelessWidget {

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text(
'You have pushed the button this many times:',
),
BlocBuilder<CounterBloc, CounterState>(builder: (context, state) {
log("state ${state.counter}");
return Center(
child: Text(
'${state.counter}',
style: TextStyle(fontSize: 40),
),
);
}),
FloatingActionButton(
onPressed: () => BlocProvider.of<CounterBloc>(context).add(CounterEvent.decrement),
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
FloatingActionButton(
onPressed: () => BlocProvider.of<CounterBloc>(context).add(CounterEvent.increment),
tooltip: 'Increment',
child: const Icon(Icons.add),
),
],
),
),
);
}

 

A MobX-os peldank lemasolva, csak BloC-al. Van 2 gombunk, egy textunk, meg egy
StatelessWidgetunk. 

Mi az ami uj?

Van egy BlocBuilderunk, itt ha a State valtozott az Event hatasara rebuildelodik (igy nem fogja az egesz UI-t refreshelni, csak azt ami itt benne van...)

BlocBuilder<CounterBloc, CounterState>(builder: (context, state) {
log("state ${state.counter}");
return Center(
child: Text(
'${state.counter}',
style: TextStyle(fontSize: 40),
),
);
}),

A builder kimenete maga az uj state.

Ha a State valtozik, tehat kiirunk LogCat-re egy Log state counter-t, ami vagy novelt vagy csokkentett ertek, es az UI ezen resze ujrabuildel.

FloatingActionButton(
onPressed: () => BlocProvider.of<CounterBloc>(context).add(CounterEvent.decrement),
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),


Es vegul: van egy FAV-unk, amire rabokve a BlocProvider-en keresztul elerheto CounterBloc -hoz hozzaadjuk a decrement vagy increment esemenyt (funkciot), ami tenylegesen noveli/csokkenti az erteket, majd visszadob egy CounterState new value-t, ami a counter erteke. Megjegyzes (BlocProvider.of<CounterBloc>(context) -et kirakhatjuk valtozoba is)

Vegul meg jojjon a State/Event classok/vagy enum-ok

enum CounterEvent { increment, decrement }

class CounterState {
final int counter;
final other;

CounterState({
this.counter = 0,
this.other = true,
});

@override
List<Object> get props => [
counter,
other,
];
}

 

Enumkent 2 funkcio, amit CSINALNI akarunk. Es maga a State, amit esetleg visszaadunk. Itt csinaltam egy other-t is, igy tudunk esetleg tobb dolgot atadni. Van aki egy State-et hasznal, van aki tobbet, van aki enumokat, van aki class-okat az eventekre.

En BaseEvent es BaseState classokat jelenleg, de tobbfele megoldas is lehet ra.

Jojjon az ertekeles:

POZITIVUM:
- state management
- nem frissul az egesz UI, mint setState-nel, hanem csak a Builder resz
- tisztabb kod
- elkulonult logika (uzleti es prezentacios reteg)
- nincs build minden egyes valtoztataskor, mint MobX-nel

NEGATIVUM:
- szenakazal meg igyis szulethet
- a vegen sok Event es State class-unk is lehet, ami mondjuk orokli az elozo Base osztalyt, es ez se biztos, hogy tul elegans megoldas (vegul (state is SomeState) -al vizsgalat)

Nekem tetszik jelenleg, ezt a megoldast hasznalom.

komment

Flutter, dart es MobX architecture

2019. december 28. 10:18 - lacas8282

Ok, Flutterezem egy ideje, kb 5 honapja, de az akkori projectet meg egyszeru MVC stilusban irtam sajat szabalyokkal. Persze mai fejjel mar lehet jobb lett volna mas szabalyokat felallitani.

Ezekre a megoldasokra keresek valaszt ebben es a kovetkezo blog posztban.

Most a MobX-rol lesz szo, ami dart ala egy allapot kezelo cucc (state management) atlathato reaktiv programozassal. A reaktiv progrmozasrol most nem beszelek, utana lehet ennek nezni.

A MobX Obszervereket, Akciokat es Reakciokat (bocs a magyarert) kulonit el, de angolul talan erdemesebb rola olvasni. (https://github.com/mobxjs/mobx.dart)

Keszitsunk Flutterbol (Android Studio) egy uj Flutter projectet. Neve legyen MobX Teszt.

A pubspec.yaml dependency-kbe masoljuk be ezeket, majd nyomjunk ra egy PACKAGES GET-et:


mobx: ^0.3.9
flutter_mobx: ^0.3.0
mobx_codegen: ^0.3.10
provider: ^3.0.0
json_serializable: ^3.0.0

dev_dependencies:
build_runner: any

Kedvenc kis counter programunkat fogjuk lekodolni, ami az alap flutter app keszitesnel is elkeszul, csakhogy MobX formaban.

Ehhez a main file tartalma ez legyen:

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MobX observer Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CounterExample(),
);
}
}


Eddig ugye semmi kulonost nem latunk, a CounterExample class meg nincs letrehozva, a MyApp widget class pedig a gyokere lesz az appunknak.

A build_runner -t azert toltottuk le a repobol, mert bizony majd buildelnunk kell a MobX generated filejait on the fly, amikor egy MobX class elkeszult, eztaltal MobX baratunk a boilerplate kodot elrejti egy valami.g.dart fileba.

A mobx_codegen pedig a MobX classokbol general .g.dart filet.

Csinalunk egy counter.dart filet. Aminek a tartalma lesz a Counter class.

import 'package:mobx/mobx.dart';

part 'counter.g.dart';

class Counter = CounterBase with _$Counter;

abstract class CounterBase with Store {
@observable
int value = 0;

@action
void increment() {
value++;
}

@action
void decrement() {
value--;
}
}

En az eredeti appot kiegeszitettem egy decrement-el, igy 2 gomb lesz az appunkban, egy ami noveli, egy ami csokkenti az erteket a valtozonak.

A MobX package-et fogjuk hasznalni ugye.

Observable es Action annotaciok jelkepezik ugye az erteket/tulajdonsagot es az akciot, amit esetleg muvelunk.
Counter classunk pedig az abstract Base class-bol ered.
Termeszetesen az Akciok async-ok is lehetnek, ha pl fetch-elni akarunk kozben vmit, varakozni ra.

@action
Future<void> loadStuff() async {
  loading = true; //This notifies observers
  stuff = await fetchStuff();
  loading = false; //This also notifies observers
}


De most maradjunk a Counter peldanknal...

Terminalba beirjuk az alabbit:

flutter packages pub run build_runner build

 

Ezutan letrejon jobb esetben a counter.g.dart file (part resz)

Igazsag szerint nekem ez a resz nem tetszik leginkabb, de mint megtudtam ezt automatara is rakhatjuk egy scriptbe Android Studio alatt BUILD elott... Fene sem akar minden egy MobX class irasa/valtoztatasa utan buildelni manualisan.

Hianyzik meg a CounterExample class ami egy Widget lesz (ugye flutterben minden IS az)

counter_page.dart filet letrehozol (ez lehet counter_widget.dart is vagy counter_component.dart is rad bizom a nevet)

Tartalma pedig:

class CounterExample extends StatefulWidget {
const CounterExample({Key key}) : super(key: key);

@override
_CounterExampleState createState() => _CounterExampleState();
}

class _CounterExampleState extends State {
final _counter = Counter();

@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(
title: const Text('Counter'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'You have pushed the button this many times:',
),
Observer(
builder: (_) => Text(
'${_counter.value}',
style: const TextStyle(fontSize: 20),
)),
FloatingActionButton(
onPressed: _counter.decrement,
tooltip: 'Decrement',
child: const Icon(Icons.remove),
),
FloatingActionButton(
onPressed: _counter.increment,
tooltip: 'Increment',
child: const Icon(Icons.add),
),
],
),
),
);
}

 

Termeszetesen az importokat alt+enterrel importoljuk. Mint lathato ez egy sima Widget, ami kirajzol 2 gombot meg egy text-et. A text tartalma egy Observer widgetbol jon, ha valtozik a _counter erteke.

A 2 gomb pedig siman noveli es csokkenti az erteket es ekkor meghivodik a state change.

POZITIVUM:
- elkulonitett kod
- nincs szenakazal
- annotaciok
- Observer widget, nice

NEGATIVUM:
- manualis build minden egyes update-kor
- a build time is idobe kerul
- .g file-ok mutatasa Android Studio alatt (talan ezt is be lehet allitani, hogy tuntesse el)

Maradjatok velem, kovetkezo cikkben a BloC-t hasznalom!:)

komment
Címkék: dart flutter mobx
süti beállítások módosítása