Programmare applicazioni mobili con flutter

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
Eccomi qui.
All'inizio mi sembrava arabo come codice, ma già fino ad oggi ho imparato un bel po' di cose, tra cui l'impaginazione grafica con elementi della libreria material.
Devo dire che mi piace usare android studio, con un cell collegato via usb e l'applicazione aperta in tempo reale.

Ho seguito le guide presenti su flutter.dev e la guida "Building beautiful UIs with Flutter" presente nei codelabs di flutter.
Ora ci sono altre cose che non riesco a trovare in questa pagina, codelab di google dedicata a flutter.

Vorrei altre guide come i codelabs, che sono molto dettagliati in merito, riguardanti le notifiche e le XHR (almeno in js si chiamano così).
Poi vorrei anche mostrare immagini all'interno dell'app, cambiare l'icona dell'app, nome, titolo e tutti metadati.

Cosa mi suggerisci?

Grazie

P.S. ti ringrazio per avermi convinto ad usare flutter, mi trovo benissimo! :brindiamo:
 

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
Per importare un'immagine non è stato difficile.
Riguardo all'icona, ho modificato pubspec, e poi creato un nuovo image asset come mostrato qui:
Schermata da 2020-06-30 23-33-31.png
Da quello che ho potuto capire nella schermata successiva, ha modificato automaticamente i vari file immagine e il manifest.
Confermami se ho fatto bene o ho sbagliato qualcosa.

Dopo ho avuto un altro problema riguardante un'altra schermata da mostrare in app (route).
Se l'intero codice della nuova pagina lo lascio nel file main.dart, non ho problemi.
Se voglio alleggerire il main e andare a scrivere le altre pagine in un altro file dart, importato correttamente, ho problemi: posso visualizzare la pagina "informazioniApplicazione" solo una volta, ma poi tornando indietro, non posso rivederla più.
Così è il mio codice
File main.dart
Codice:
void _vediinfo() {
    Navigator.of(context).push(informazioniApplicazione);
  }
File altrepagine.dart
Codice:
import 'variabili.dart';
import 'package:flutter/material.dart';

MaterialPageRoute informazioniApplicazione = MaterialPageRoute<void>(
  builder: (BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(testi['paginfoapptit']),
        elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
      ),
      body: Container(
        decoration: lineaGrigiaSuperiore(context),
        child: Column(
          children: [
            Text(applicazione['nome']),
            Image(
              image: AssetImage('immagini/logo-flutter.png'),
            ),
          ],
        ),
      ),
    );
  },
);

Tenendo conto che l'analizzatore dart non mi segnala errori, dove sbaglio?

Grazie
 

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
Il resto, ora lo invio qui.

File main.dart
Codice:
import 'variabili.dart';
import 'moduliiniziali.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(MiaApp());
}

class MiaApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: testi['titolo'],
      theme: temaGeneraleAndroid,
      home: SchermataIniziale(),
    );
  }
}

class SchermataIniziale extends StatefulWidget {
  @override
  _SchermataInizialeState createState() => _SchermataInizialeState();
}

class _SchermataInizialeState extends State<SchermataIniziale> {
  int _counter = 0;

  void _vediinfo() {
    Navigator.of(context).push(informazioniApplicazione);
  }

  // PAGINA INIZIALE
 @override
  Widget build(BuildContext context) {
    // This method is rerun every time setState is called, for instance as done
    // by the _incrementCounter method above.
    //
    // The Flutter framework has been optimized to make rerunning build methods
    // fast, so that you can just rebuild anything that needs updating rather
    // than having to individually change instances of widgets.
    return Scaffold(
      appBar: AppBar(
        actions: [
          IconButton(
              icon: Icon(Icons.menu),
              onPressed: aprimenu,
              tooltip: testi['aprimenu']),
        ],
        title: Text(testi['titolo']),
        elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
      ),
      body: Container(
        decoration: lineaGrigiaSuperiore(context),
        child: Center(
          // Center is a layout widget. It takes a single child and positions it
          // in the middle of the parent.
          child: Column(
            // Column is also a layout widget. It takes a list of children and
            // arranges them vertically. By default, it sizes itself to fit its
            // children horizontally, and tries to be as tall as its parent.
            //
            // Invoke "debug painting" (press "p" in the console, choose the
            // "Toggle Debug Paint" action from the Flutter Inspector in Android
            // Studio, or the "Toggle Debug Paint" command in Visual Studio Code)
            // to see the wireframe for each widget.
            //
            // Column has various properties to control how it sizes itself and
            // how it positions its children. Here we use mainAxisAlignment to
            // center the children vertically; the main axis here is the vertical
            // axis because Columns are vertical (the cross axis would be
            // horizontal).
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              Text(
                'You have pushed the button this many times:',
              ),
              Text(
                '$_counter',
                style: Theme.of(context).textTheme.headline4,
              ),
            ],
          ),
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _vediinfo,
        tooltip: 'Informazioni',
        child: Icon(Icons.info),
      ), // This trailing comma makes auto-formatting nicer for build methods.
    );
  }
}

File variabili.dart
Codice:
import 'package:flutter/material.dart';

final applicazione = const {
  // parte informativa
  'nome': '**miei dati**',
  'versione':
      '0.1alfa',
  'rilascio':
      '23/6/20', 
  'dominio': '**miei dati**',

  // parte tecnica
  'sviluppo': true,
  'stattraffico': false,
  'pubblicita': false,

};

final testi = {
  'titolo': applicazione['nome'],
  'benv': 'Grazie per aver installato ' + applicazione['nome'],
  'aprimenu': 'Apri menu',
  'paginfoapptit': 'Informazioni su ' + applicazione['nome'],
};

final ThemeData temaGeneraleAndroid = ThemeData(
    primarySwatch: Colors.deepOrange,
    visualDensity: VisualDensity.adaptivePlatformDensity);

final TextStyle infoApp = TextStyle(letterSpacing: 1.5, color: Colors.blue);
final TextStyle nomiImpostazioni = TextStyle(
    fontWeight: FontWeight.bold, color: temaGeneraleAndroid.primaryColor);
final TextStyle vociImpostazioni = TextStyle(color: Colors.black);

dynamic lineaGrigiaSuperiore(contesto) => Theme.of(contesto).platform == TargetPlatform.iOS
    ? BoxDecoration(
  border: Border(
    top: BorderSide(color: Colors.grey[500]),
  ),
)
    : null;

File moduliiniziali.dart
Codice:
import 'variabili.dart';
import 'package:flutter/material.dart';

void aprimenu() {}

MaterialPageRoute informazioniApplicazione = MaterialPageRoute<void>(
  builder: (BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(testi['paginfoapptit']),
        elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
      ),
      body: Container(
        decoration: lineaGrigiaSuperiore(context),
        child: Column(
          children: [
            Text(applicazione['nome']),
            Image(
              image: AssetImage('immagini/logo-flutter.png'),
            ),
          ],
        ),
      ),
    );
  },
);

Come puoi vedere, c'è il codice predefinito di android studio di un progetto nuovo, non ho fatto grandi modifiche.

Dove ho sbagliato?

Grazie
 

pabloski

Utente Èlite
2,868
916
Il problema è che l'oggetto MaterialPageRoute viene rimosso dallo stack dei route e distrutto, non appena premi la freccia per tornare indietro.

In questo modo, la variabile informazioniApplicazione diventa null e ovviamente non conterrà più niente.

E ho capito pure perchè hai fatto così. Quello sarebbe codice Javascript validissimo, ma non lo è in Dart.
 
  • Mi piace
Reazioni: Moffetta88

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
Quello sarebbe codice Javascript validissimo, ma non lo è in Dart.
Come hai potuto dedurre, provenendo dallo sviluppo web, ho fatto 2+2 e ho provato a fare la stessa cosa in dart, anche perché android studio non mi ha segnalato niente, quindi ho dato per scontato che fosse valido.
Il problema è che l'oggetto MaterialPageRoute viene rimosso dallo stack dei route e distrutto, non appena premi la freccia per tornare indietro.

In questo modo, la variabile informazioniApplicazione diventa null e ovviamente non conterrà più niente.
Non potendo fare come ho fatto, come posso portare intere pagine di app in altri file dart, in modo da non tenere tutto in main?

Grazie
 

pabloski

Utente Èlite
2,868
916
Come hai potuto dedurre, provenendo dallo sviluppo web, ho fatto 2+2 e ho provato a fare la stessa cosa in dart, anche perché android studio non mi ha segnalato niente, quindi ho dato per scontato che fosse valido.

E infatti non è sbagliato.

Non potendo fare come ho fatto, come posso portare intere pagine di app in altri file dart, in modo da non tenere tutto in main?

Non è il problema del main, è che quell'oggetto viene distrutto appena ritorni indietro con la navigazione. Praticamente ne devi istanziare uno nuovo, ogni volta che si preme il pulsante.
 

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
E come posso istanziare un oggetto nuovo portando il codice in un altro file dart?
Mi fai un esempio?

Grazie
 

pabloski

Utente Èlite
2,868
916
Lì il problema è che fai confusione tra l'oggetto route e i contenuti che ci vengono messi dentro. Hai trattato MaterialPageRoute, come se indicasse il contenuto, ma invece è il gestore delle route.

Non so come spiegarmi meglio. In pratica, in moduliiniziali.dart ci va questo

JavaScript:
class MiaRoute extends StatelessWidget {
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(testi['paginfoapptit']),
        elevation: Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
      ),
      body: Container(
        decoration: lineaGrigiaSuperiore(context),
        child: Column(
          children: [
            Text(applicazione['nome']),
            Image(
              image: AssetImage('immagini/logo-flutter.png'),
            ),
          ],
        ),
      ),
    );
  }
}

in _vediinfo questo

JavaScript:
Navigator.of(context).push(MaterialPageRoute(builder: (context) => MiaRoute()));

Lì, l'oggetto MiaRoute viene costruito ogni volta che viene chiamata push. E distrutto ogni volta che viene chiamata pop, quest'ultima invocata implicitamente quando ritorni indietro alla pagina precedente.

A questo punto, è meglio se fai una piccola deviazione sulla teoria https://medium.com/flutter-community/flutter-routes-and-navigation-69f128a9ea8f
 

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
Ho letto la guida da te postata e ho trovato interessante questo codice:
JavaScript:
void main() {
  runApp(new MaterialApp(
    home: new Landing(),
    //new
    routes: <String, WidgetBuilder>{
      '/page2': (BuildContext context) => new Page2(),
      '/page3': (BuildContext context) => new Page3()
    },
  ));
}
Ma c'è una cosa che non ho capito: le pagine 2 e 3 vengono elaborate all'avvio dell'app o ogni volta che voglio accedere?
Questo mi serve saperlo per capire cosa posso metterci dentro (variabili, contenuti da remoto, ecc...).

Grazie
 

pabloski

Utente Èlite
2,868
916
Considera cosa fa quel new. In pratica viene istanziato un nuovo oggetto ad ogni accesso.

Inutile dire che => indica un'arrow function, cioè una funzione anonima.
 
  • Mi piace
Reazioni: Moffetta88

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
Se ho capito bene:
  1. le routes dichiarate all'inizio, come pag2 e 3 sono statiche, perché istanziate all'avvio dell'app, e in ambito web corrispondono ad un url diverso (es. /, /info o /contatti)
  2. all'interno di queste routes posso lasciare spazi da assegnare dinamicamente in base a ciò che fa l'utente (non so come, ma penso sia /#lista, /contatti#scrivi), cioè pezzi di route che cambiano, e posso fare ogni route dichiarata in main() come StatefulWidget
Ho preso un'altra cantonata?

Grazie
 

pabloski

Utente Èlite
2,868
916
  1. le routes dichiarate all'inizio, come pag2 e 3 sono statiche, perché istanziate all'avvio dell'app, e in ambito web corrispondono ad un url diverso (es. /, /info o /contatti)

Statiche nel senso che sono definite all'inizio e poi lasciate lì. Ma fai caso che ciò che viene passata, è una funzione anonima che crea il relativo oggetto, ogni volta che viene selezionata quella route.

Cioè, a livello di funzionamento sono identiche alle altre. Solo la home è speciale, perchè l'oggetto non viene distrutto mai.

  1. all'interno di queste routes posso lasciare spazi da assegnare dinamicamente in base a ciò che fa l'utente (non so come, ma penso sia /#lista, /contatti#scrivi), cioè pezzi di route che cambiano, e posso fare ogni route dichiarata in main() come StatefulWidget

il simbolo # nei link html è chiamato named anchor o fragment, ed è un artificio...Flutter non lo usa e non ne ha bisogno

Flutter usa la convenzione delle sottodirectory, cioè /route1/sottoroute1, ecc...

e ovviamente l'organizzazione è totalmente a discrezione del programmatore

e si, le route possono essere dichiarate nel main, almeno quelle che non vengono create al volo, in base allo stato di esecuzione del programma
 
  • Mi piace
Reazioni: Moffetta88

hddsfortuna

Utente Attivo
869
39
Net
FTTH 1000/300
OS
Ubuntu 22.04
Bene, ora sto seguendo questo codelab, il primo di 4 che fa creare un'app di commercio elettronico. Appena finisco tutti e 4 i codelab, avrò imparato più cose rispetto ad ora.

Nel frattempo però, dilettandomi con la mia app di cui ho postato il codice nei post precedenti, sto avendo problemi.
Ho rinominato moduliiniziali in pagine, di cui questo è il nuovo contenuto:
JavaScript:
import 'variabili.dart';
import 'package:flutter/material.dart';

final List<SingolaInfo> _listaInfo = [
  SingolaInfo(nome: 'Versione App', valore: '1.0.0'),
  SingolaInfo(nome: 'Rilascio App', valore: '1/7/20'),
];

class SingolaInfo extends StatelessWidget {
  SingolaInfo({this.nome, this.valore});
  final String nome;
  final String valore;

  @override
  Widget build(BuildContext context) => Expanded(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.start,
          children: [
            Text(
              nome,
              style: nomiImpostazioni,
            ),
            Text(
              valore,
              style: vociImpostazioni,
            ),
            Divider(height: 1.0),
          ],
        ),
      );
}

class PagInformazioniApplicazione extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text(testi['paginfoapptit']),
          elevation:
              Theme.of(context).platform == TargetPlatform.iOS ? 0.0 : 4.0,
        ),
        body: Container(
          decoration: lineaGrigiaSuperiore(context),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.start,
            children: [
              Text(applicazione['nome']),
              Image(
                image: AssetImage('immagini/logo-flutter.png'),
              ),
              Flexible(
                child: ListView.builder(
                  //padding: EdgeInsetsDirectional.fromSTEB(10.0, 4.0, 10.0, 4.0),
                  itemBuilder: (_, i) => _listaInfo[i],
                  itemCount: _listaInfo.length,
                ),
              )
            ],
          ),
        ),
      );
}
Nel file main ho tolto la funzione vediinfo() e cambiato la classe principale del widget:
JavaScript:
class MiaApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: testi['titolo'],
      theme: temaGeneraleAndroid,
      home: SchermataIniziale(),
      routes: <String, WidgetBuilder>{
        '/infoapp': (BuildContext context) => PagInformazioniApplicazione(),
      },
    );
  }
}

[...]

floatingActionButton: FloatingActionButton(
        onPressed: () => Navigator.of(context).pushNamed('/infoapp'),
        tooltip: 'Informazioni',
        child: Icon(Icons.info),
      ),
Il file variabili non è cambiato.
Ricevo questo errore qui nella console di android studio:
Codice:
Performing hot restart...
Syncing files to device SM J120FN...
Restarted application in 2.245ms.
D/ViewRootImpl( 9680): ViewPostImeInputStage ACTION_DOWN

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown while applying parent data.:
Incorrect use of ParentDataWidget.

The ParentDataWidget Expanded(flex: 1) wants to apply ParentData of type FlexParentData to a RenderObject, which has been set up to accept ParentData of incompatible type ParentData.

Usually, this means that the Expanded widget has the wrong ancestor RenderObjectWidget. Typically, Expanded widgets are placed directly inside Flex widgets.
The offending Expanded is currently placed inside a RepaintBoundary widget.

The ownership chain for the RenderObject that received the incompatible parent data was:
  Column ← Expanded ← SingolaInfo ← RepaintBoundary ← IndexedSemantics ← NotificationListener<KeepAliveNotification> ← KeepAlive ← AutomaticKeepAlive ← KeyedSubtree ← SliverList ← ⋯
When the exception was thrown, this was the stack:
#0      RenderObjectElement._updateParentData.<anonymous closure> (package:flutter/src/widgets/framework.dart:5645:11)
#1      RenderObjectElement._updateParentData (package:flutter/src/widgets/framework.dart:5661:6)
#2      RenderObjectElement.attachRenderObject (package:flutter/src/widgets/framework.dart:5682:7)
#3      RenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5376:5)
#4      MultiChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:5943:11)
...
════════════════════════════════════════════════════════════════════════════════════════════════════

════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
Incorrect use of ParentDataWidget.
════════════════════════════════════════════════════════════════════════════════════════════════════
Premesso che sul cell non vedo malfunzionamenti, e nell'analisi di android studio non viene riportato nessun errore.
E' vero che provengo dallo sviluppo web, ma penso che un po' di logica riguardo qualsiasi programmazione la sappia fare.

Mi diresti come correggere questo problema?

Grazie
 

Entra

oppure Accedi utilizzando
Discord Ufficiale Entra ora!