Całościowe podejście do projektowania aplikacji mobilnych

Całościowe podejście do projektowania aplikacji mobilnych
B0
  • Rejestracja:ponad 5 lat
  • Ostatnio:około rok
  • Postów:25
0

Jako że jestem zupełnie początkującym programistą ale z 30+ letnim doświadczeniem w IT, mam pytanie do osób, które już znają Fluttera.

Chciałbym napisać bazodanową aplikacje mobilną we Flutterze, która korzystała by z REST API.
Aplikacja zbudowana była by z kilku ekranów, wymagała by logowania przez REST API (skrypty php po stronie serwera na już działającej stronie), wyświetlała by informacje z bazy, dodawała nowe wpisy i edytowała istniejące... zupełnie standardowe rozwiązanie.

Mam bardzo wyrywkową wiedzę zaczerpniętą z https://docs.flutter.dev i YT na temat korzystania z REST API i brak mi wiedzy ogólnej na temat całościowego podejścia do problemu.

Czy mógłby mi ktoś najpierw pomóc zidentyfikować technologie jakich będę musiał użyć w aplikacji? Jak już będę wiedział czego szukać w szczegółach to jakoś znajdę w google ;)

Może ja zacznę od problemów/pytań jakie mam:

  1. Jak wygląda proces uwierzytelniania w UI/Rest API: wiem jak wywołać skrypt PHP i jak pobrać z niego dane, ale nie rozumiem ogólnego procesu tzn. jakie dane wysłać(login/password)/pobarać(sucess???, token??? itd.) ze API...
  2. Jaki ogarnąć problem kontroli sesji PHP w UI (shared_preferences???)
  3. Jakie są typowe techniki programistyczne w tego typu aplikacjach?

Z góry serdecznie dziękuję za pomoc.

edytowany 2x, ostatnio: Riddle
M0
  • Rejestracja:ponad 11 lat
  • Ostatnio:minuta
  • Postów:362
0
  1. Najprościej to wykorzystać token JWT. Na backend wysyłasz login i hasło, autoryzujesz i generujesz token JWT(albo zwracasz błąd). Potem ten token przesyłasz w nagłówku i na backendzie sprawdzasz czy token jest poprawny.
  2. REST API jest bezstanowy, więc nie ma czegoś takiego jak sesja. Nie trzymasz danych sesji usera na backendzie.
B0
  • Rejestracja:ponad 5 lat
  • Ostatnio:około rok
  • Postów:25
0

OK... rozumiem... a czy jest jakaś biblioteka JWT czy kod implementujemy sami?

Tak się zastanawiam... jak długo w jest ważny token JWT, bo patrząc od strony UI, to chciałbym żeby po zalogowaniu UI mogło wykonywać zapytania do API aż do momentu wylogowania (co może trwać np. miesiąc lub więcej). Czy w takiej sytuacji ustawia się np. długi czas ważności tokena czy tworzy się mechanizm jego odświeżenia?

G8
  • Rejestracja:około 3 lata
  • Ostatnio:około rok
  • Postów:2000
2

@Michalk001 coś pomieszałeś. Nie ma przeciwwskazań, żeby przesyłać cookie / sesję zamiast przesyłać tokeny w nagłówkach. Klient rest to taki sam klient http jak przeglądarka, tyle że mamy jsony zamiast html-a. Często się tak robi, po bo po się kopać z jakimiś tokenami w nagłówkach, gdy klient http sam będzie zarządzać sesją i przesyłać cookie z na serwer. Cookie może być też zapisywane w pamięci urządzenia, automatycznie (tak samo jak to działa w przeglądarce). Tokeny musisz sam zapisywać w shared preferences i potem ładować do nagłówków.

Co do tematu (klient we Flutterze), zalecałbym taki zestaw:

Jak to wszystko poskładać masz np tu: https://medium.com/mindful-engineering/retrofit-the-easiest-way-to-call-rest-apis-is-flutter-fe55d1e7c5c2 albo zajrzyj do dokumentacji Retrofit. Szczerze ci radzę użyć Retrofit, bo ręczne skrobanie tego, zwłaszcza jak masz jakieś bardziej skomplikowane rzeczy jak przesyłanie formularzy, plików, query, itd jest dość upierdliwe. Podobnie z modelami do serializacji.

Zarządzanie sesją przez cookie wygląda tak, że najpierw klient wywołuje jakiegoś POST-a, w którym przesyła swoje poświadczenia (user/password np). Dalej standardowo, serwer tworzy sesję i przesyłą cookie. Tego cookie używasz w kliencie http (cookie manager), wszystko się dzieje automatycznie. Gdy cookie zostanie unieważnione (unieważniona sesja), to autoryzowne requesty powinny zwracać 403, wtedy musisz w kliencie utworzyć nową sesję (wywołać odpowiedni POST i dostać nowe cookie)

edytowany 8x, ostatnio: gajusz800
SO
Ale JWT teraz modne, pomimo że często w scenariuszach, gdzie front komunikuje się z backendem nic nie wnosi i i tak musisz je obudowywać mechanizmami sesyjnymi :D O tym, że każde API HTTP nazywane jest REST API nie wspomnę.
M0
Ale ja nigdzie nie napisałem, że takie coś jest niedozwolone. Autor pisał o Rest API, procesie uwierzytelniania oraz trzymaniu sesji po stronie backendu.
B0
  • Rejestracja:ponad 5 lat
  • Ostatnio:około rok
  • Postów:25
0

no pięknie... a ja już wygenerowałem token na backendzie (https://github.com/firebase/php-jwt) i zacząłem robić frontend.... może jeszcze poczekam... ;)
co do cookies, czytałem o cookie_manager'ze i wygląda to fajnie, ale gdzieś na forum widziałem, że ktoś pisał że SESSID mu się bardzo często zmienia :(

G8
  • Rejestracja:około 3 lata
  • Ostatnio:około rok
  • Postów:2000
0

Jeśli masz token, to musisz go sam wrzucać w shared preferences, a żeby nie trzeba było go doklejać do każdego requestu, wrzuć go w interceptor w kliencie http to będzie automatycznie dodawany. Tyle że musisz sam tym zarządzać, zapisywać do shared preferences itd.

Tu masz pokazane jak to zrobić na adnotacjach albo z interceptorem: https://medium.com/flutter-community/hocus-pocus-painless-headers-customization-of-rest-api-requests-in-flutter-5ee9c1a2d9f8

edytowany 1x, ostatnio: gajusz800
marian pazdzioch
  • Rejestracja:ponad 6 lat
  • Ostatnio:około 5 godzin
  • Postów:713
0
  1. to zależy od tego z jakim serwisem gadasz (nie mamy oczywiście wglądu w to co nazywasz, dość zabawnie, skryptami PHP i co one tam potrafią i wymagają)
  2. to też zależy, każde podejście ma swoje cechy szczególne (można trzymać w SP, ale to od razu komplikuje sprawy, można trzymać w RAM, można nie trzymać nigdzie)
  3. nie ma czegoś takiego

Czy odpowiedziałeś sobie na pytanie czemu w ogóle rozważasz jak się domyślam kolejną aplikację (we Flutterze czy czymkolwiek innym)? Skoro tam jest już jakiś BE to pewnie jest też jakoś FE, czemu nie użyć tego zamiast wytwarzać otwarte drzwi?

B0
  • Rejestracja:ponad 5 lat
  • Ostatnio:około rok
  • Postów:25
0

OK... poniżej załączam trochę moich wypocin z tutoriali z googla :)
Nie mogę sobie poradzić z przejściem ze screena LoginPage do HomePage (po poprawnym zalogowaniu).

Próbowałem: Navigator.of(context).push(MaterialPageRoute(builder: (context) => HomePage()));
ale dostałem błąd: "type 'Future' is not a subtype of type '(() => dynamic)?'"

Czy moglibyście na to zerknąć, proszę i doradzić co może być nie tak?

Dzięki

Poniżej kod LoginPage:

Kopiuj
class LoginPage extends StatelessWidget {
  LoginPage({super.key});

  final emailController = TextEditingController();
  final passwordController = TextEditingController();

  void signUserIn() async  {

    try{
      Response response = await post(
          Uri.parse('https://www.somepage.com/login.php'),
          body: {
            'email' : emailController.text,
            'password' : passwordController.text
          }
      );

      if(response.statusCode == 200){
        var data = jsonDecode(response.body.toString());
        if(data['success'] == 1) {
          print(data['message']);
          
          SharedPreferences prefs = await SharedPreferences.getInstance();
          prefs.setString('email', emailController.text);
          prefs.setString('token', data['token']);



          // ---> tutaj chciałbym przejść do HomePage 

          


        } else {
          print(data['status']);
          print(data['message']);
        }
      }else {
        print('Login failed...');
        print(response.statusCode);
      }
    }catch(e){
      print(e.toString());
    }

  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[300],
      body: SafeArea(
        child: Center(
          child: SingleChildScrollView(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                const SizedBox(height: 25),

                const Icon(
                  Icons.lock,
                  size: 100,
                ),

                const SizedBox(height: 25),

                Text(
                  'Witamy ponownie!',
                  style: TextStyle(
                    color: Colors.grey[700],
                    fontSize: 16,
                  ),
                ),

                const SizedBox(height: 25),

                MyTextField(
                  controller: emailController,
                  hintText: 'Email',
                  obscureText: false,
                ),

                const SizedBox(height: 10),

                MyTextField(
                  controller: passwordController,
                  hintText: 'Hasło',
                  obscureText: true,
                ),

                const SizedBox(height: 10),

                const Padding(
                  padding: EdgeInsets.symmetric(horizontal: 25.0),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      Text(
                        'Nie pamiętasz hasła?',
                        style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
                      ),
                    ],
                  ),
                ),

                const SizedBox(height: 20),

                MyButton(
                  onTap: signUserIn,
                ),

                const SizedBox(height: 30),

                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 25.0),
                  child: Row(
                    children: [
                      Expanded(
                        child: Divider(
                          thickness: 0.5,
                          color: Colors.grey[400],
                        ),
                      ),
                      Padding(
                        padding: const EdgeInsets.symmetric(horizontal: 25.0),
                        child: Text(
                          'Albo kontynuuj z',
                          style: TextStyle(color: Colors.grey[700]),
                        ),
                      ),
                      Expanded(
                        child: Divider(
                          thickness: 0.5,
                          color: Colors.grey[400],
                        ),
                      ),
                    ],
                  ),
                ),

                const SizedBox(height: 30),

                const Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    SquareTile(imagePath: 'lib/images/google.png'),  
                    SizedBox(width: 10),
                    SquareTile(imagePath: 'lib/images/apple.png')
                  ],
                ),

                const SizedBox(height: 30),

                Row(
                  mainAxisAlignment: MainAxisAlignment.center,
                  children: [
                    Text(
                      'Nie masz konta?',
                      style: TextStyle(color: Colors.grey[700]),
                    ),
                    const SizedBox(width: 4),
                    const Text(
                      'Zarejestruj się',
                      style: TextStyle(color: Colors.blue, fontWeight: FontWeight.bold),
                    ),
                  ],
                )
              ],
          ),
        ),
      ),
    ),
  );
 }
}

i kod HomePage:

Kopiuj
class HomePage extends StatelessWidget {
  HomePage({super.key});

  void signUserOut() async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    prefs.remove('email');
    prefs.remove('token');
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.grey[300],
      appBar: AppBar(
        backgroundColor: Colors.grey[900],
        actions: [
          IconButton(
            onPressed: signUserOut,
            icon: Icon(Icons.logout),
          )
        ],
      ),
      body: Center(
          child: Text(
        "TEST",
        style: TextStyle(fontSize: 20),
      )),
    );
  }
}
B0
  • Rejestracja:ponad 5 lat
  • Ostatnio:około rok
  • Postów:25
0
B0
  • Rejestracja:ponad 5 lat
  • Ostatnio:około rok
  • Postów:25
0

OK... trochę poczytałem i coś z tego zaczyna wychodzić. Mam jednak jeden problem. Nie potrafię zmapować struktur danych do itemów w DropdownButtonFormField.
Czy mógłby ktoś pomóc mi z tym bo utknąłem...

Struktura danych wygląda tak:

Kopiuj
class PostListOfDiets {
  String? nazwa;
  String? opis;
  String? komentarz;
  String? dietetyk;
  int? id;

  PostListOfDiets({this.nazwa, this.opis, this.komentarz, this.dietetyk, this.id});

  PostListOfDiets.fromJson(Map<String, dynamic> json) {
    nazwa = json['nazwa'];
    opis = json['opis'];
    komentarz = json['komentarz'];
    dietetyk = json['dietetyk'];
    id = json['id'];
  }
}

a menu tworzę w ten sposób:

Kopiuj
            child: DropdownButtonFormField(
                  hint: const Text("Wybierz jadłospis"),
                  
                  // >>> tu jest część której nie rozumiem
                  items: posts.map<DropdownMenuItem<String>>((String value) {
                  return DropdownMenuItem<String>(
                    value: value,
                    child: Text(value),
                  );
                  }).toList(),
                  //<<<tu jest część której nie rozumiem
                  
                  onChanged: (val) {
                    setState(() {
                      _selectedVal = val as String;
                    });
                  },
                  icon: const Icon(
                    Icons.arrow_drop_down_circle,
                    color: Colors.blue,
                  ),
                  dropdownColor: Colors.grey[200],
                  decoration: const InputDecoration(
                    labelText: "Nazwa jadłospisu",
                    labelStyle: TextStyle(color: Colors.blue, fontSize: 16.0),
                    prefixIcon: Icon(
                      Icons.list_alt,
                      color: Colors.blue,
                    ),
                  ),
                )

Dostaje błąd: Error: The argument type 'DropdownMenuItem<String> Function(String)' can't be assigned to the parameter type 'DropdownMenuItem<String> Function(PostListOfDiets)'.

Czy mogę prosić o pomoc... ?

B0
  • Rejestracja:ponad 5 lat
  • Ostatnio:około rok
  • Postów:25
0

kombinowałem i wykombinowałem coś takiego...

Kopiuj
items: posts.map((item) {
                    return DropdownMenuItem(
                      value: item.id.toString(),
                      child: Text(item.nazwa.toString()),
                    );
                  }).toList(),

Wygląda na banalnie proste... ale czy dobrze kombinuje...?

CZ
ChceszZeMnieKpić
Dobrze. Teraz to mapowanie ma sens.
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)