To say my favorite game, it has to be League of Legends. So many memories! Today we are working with Flutter to develop a hero profile card. Above is a partial screenshot of the APP, and the overall design of the APP looks pretty clean. On the home page, Tab is used to display the six categories of heroes. Click the hero entry to jump to the hero details page.

The directory structure

- lib
    - models
    - utils
    - views
    - widgets
    - main.dart
Copy the code

Let’s start with the directory structure of the project, to grasp the APP as a whole. The directory structure we use in this APP is very common. Not only Flutter development, but also the current front-end development mode is basically similar:

  • modelsTo define the data model
  • utilsPut some common functions, interfaces, routes, constants, etc
  • viewsIt puts page-level components in it
  • widgetsIt contains the widgets you need to use on the page
  • main.dartIt’s the startup file of your APP

At the beginning of the

Dart must start from main.dart, and some of the template code will be omitted. One thing to note is that the background of the APP status bar is transparent, which is configured in the main() function:

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(MyApp());
  if(Platform.isAndroid) { SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(statusBarColor: Colors.transparent); SystemChrome.setSystemUIOverlayStyle(systemUiOverlayStyle); }}Copy the code

Home page

After the APP enters the home page, it starts to pull the data of the back-end interface to display the list of heroes. The TabBar component defines Tab switching at the top of the page, and the TabBarView displays the list at the bottom of the page. Originally intended to use the fist open interface data, but did not provide Chinese translation. Tencent went to find the next, Tencent is more closed, there is no developer interface. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * In addition, my server configuration is not very high and unstable, so the interface for learning to use oh

import 'package:flutter/material.dart';
import 'package:lol/views/homeList.dart';
import 'package:lol/utils/api.dart' as api;
import 'package:lol/utils/constant.dart';
import 'package:lol/utils/utils.dart';

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

  _HomeViewState createState() => _HomeViewState();
}

class _HomeViewState extends State<HomeView> with SingleTickerProviderStateMixin {
  TabController _tabController;
  List<dynamic> heroList = [];

  @override
  void initState() {
    super.initState();
    _tabController = TabController(vsync: this, initialIndex: 0, length: 6);
    init();
  }

  init() async {
    Map res = await api.getHeroList();
    setState(() {
     heroList = res.values.toList();
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: TabBar(
          controller: _tabController,
          tabs: <Widget>[
            Tab(text: 'soldiers'),
            Tab(text: 'the tanks'),
            Tab(text: 'master'),
            Tab(text: 'the assassin'),
            Tab(text: 'secondary'),
            Tab(text: 'striker'), ], ), ), body: TabBarView( controller: _tabController, children: <Widget>[ HomeList(data: Utils.filterHeroByTag(heroList, Tags.Fighter)), HomeList(data: Utils.filterHeroByTag(heroList, Tags.Tank)), HomeList(data: Utils.filterHeroByTag(heroList, Tags.Mage)), HomeList(data: Utils.filterHeroByTag(heroList, Tags.Assassin)), HomeList(data: Utils.filterHeroByTag(heroList, Tags.Support)), HomeList(data: Utils.filterHeroByTag(heroList, Tags.Marksman)), ], ), ); }}Copy the code

Home page list

Six of the first page of the list is the same, just different data, so the public a component homeList. The dart to switch the Tab page needs to be before the time in order not to destroy the component inheritance AutomaticKeepAliveClientMixin class:

import 'package:flutter/material.dart';
import 'package:lol/widgets/home/heroItem.dart';
import 'package:lol/models/heroSimple.dart';

class HomeList extends StatefulWidget {
  final List data;
  HomeList({Key key, this.data}) : super(key: key);

  _HomeListState createState() => _HomeListState();
}

class _HomeListState extends State<HomeList>
    with AutomaticKeepAliveClientMixin {
  @override
  bool get wantKeepAlive => true;

  @override
  Widget build(BuildContext context) {
    super.build(context);
    return Container(
      padding: EdgeInsets.symmetric(vertical: 5),
      child: ListView.builder(
        itemCount: widget.data.length,
        itemBuilder: (BuildContext context, int index) {
          returnHeroItem(data: HeroSimple.fromJson(widget.data[index])); },),); }}Copy the code

Details of the hero

Dart contains a number of widgets, including a skin preview that uses a third-party image-viewing library called Extended_Image, which is a very powerful and must be supported by a Chinese developer.

import 'package:flutter/material.dart';
import 'package:lol/utils/api.dart' as api;
import 'package:lol/models/heroSimple.dart';
import 'package:lol/models/heroDetail.dart';
import 'package:lol/utils/utils.dart';
import 'package:lol/widgets/detail/detailItem.dart';
import 'package:lol/widgets/detail/skin.dart';
import 'package:lol/widgets/detail/info.dart';

class HeroDetail extends StatefulWidget {
  final HeroSimple heroSimple;
  HeroDetail({Key key, this.heroSimple}) : super(key: key);

  _HeroDetailState createState() => _HeroDetailState();
}

class _HeroDetailState extends State<HeroDetail> {
  HeroDetailModel _heroData; / / hero data
  bool _loading = false; // Load status
  String _version = ' '; // Chinese version
  String _updated = ' '; // Document update time

  @override
  void initState() {
    super.initState();
    init();
  }

  init() async {
    setState(() {
      _loading = true;
    });
    Map res = await api.getHeroDetail(widget.heroSimple.id);
    var data = res['data'];
    String version = res['version'];
    String updated = res['updated'];
    print(version);
    setState(() {
      _heroData = HeroDetailModel.fromJson(data);
      _version = version;
      _updated = updated;
      _loading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(widget.heroSimple.name), elevation: 0),
      body: _loading
          ? Center(child: CircularProgressIndicator())
          : SingleChildScrollView(
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: <Widget>[
                  DetailItem(
                    title: 'skin',
                    child: Skins(imgList: _heroData.skins),
                  ),
                  DetailItem(
                    title: 'type',
                    child: Row(
                        children: _heroData.tags
                            .map((tag) => Container(
                                  margin: EdgeInsets.only(right: 10),
                                  child: CircleAvatar(
                                    child: Text(
                                      Utils.heroTagsMap(tag),
                                      style: TextStyle(color: Colors.white),
                                    ),
                                  ),
                                ))
                            .toList()),
                  ),
                  DetailItem(
                    title: 'properties',
                    child: HeroInfo(data: _heroData.info),
                  ),
                  DetailItem(
                    title: 'Use tips',
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: _heroData.allytips
                          .map((tip) => Column(
                                children: <Widget>[
                                  Text(tip),
                                  SizedBox(height: 5)
                                ],
                              ))
                          .toList(),
                    ),
                  ),
                  DetailItem(
                    title: 'Fighting Skills',
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: _heroData.enemytips
                          .map((tip) => Column(
                                children: <Widget>[
                                  Text(tip),
                                  SizedBox(height: 5)
                                ],
                              ))
                          .toList(),
                    ),
                  ),
                  DetailItem(
                    title: 'Back story',
                    child: Text(_heroData.lore),
                  ),
                  DetailItem(
                    title: 'Chinese Version',
                    child: Text(_version),
                  ),
                  DetailItem(
                    title: 'Update Time', child: Text(_updated), ) ], ), ), ); }}Copy the code

Packaging APK

Packaging APK typically requires three steps:

Step1: generate a signature

Step2: configure the signature for the project

Step3: packaging

Step1: generate a signature

Before packaging APK, you need to generate a signature file, which is the unique identity of the APP:

keytool -genkey -v -keystore c:/Users/15897/Desktop/key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias key
Copy the code
  • c:/Users/15897/Desktop/key.jksRepresents the file generated location that I set directly on the desktop
  • -validity 10000Indicates the validity period of the signature
  • -alias keyTo give the signature file a name, I’ll just set it to key

After executing this command line, there is an interactive q&A:

Enter keystore password: Enter new password again: what is your first and last name? Hua What is the name of your organization? [Unknown]: XXX What is your organization name? [Unknown]: XXX What is the name of your city or region? [Unknown]: XXX What is the name of your province/city/autonomous region? [Unknown]: XXX What is the two-letter country/area code of the unit? Unknown: XXX CN=hua, OU= XXX, O= XXX, L= XXX, ST= XXX, C= XXX [No]: y is generating a 2,048-bit RSA key pair and self-signed certificate (SHA256withRSA) (valid for 10,000 days) for the following objects: CN = hua, OU = XXX, O = XXX, L = XXX, ST = XXX, C = XXX enter < key > key password (if is the same as the keystore password and press enter) : [are stored C: / Users / 15897 / Desktop/key JKS]Copy the code

Step2: configure the signature for the project

In the project of new file < app dir > / android/key properties, file defines four variables, keep to the < app dir > / android/app/build. Gradle calls.

Step on the first three are used in several fields, the fourth storeFile is the location of the signature file, the file location is relative to the < app dir > / android/app/build. Gradle, So copy key. JKS generated in the previous step to

/android/app/.

Warning: Files involve passwords and so should not be uploaded to version control.

# Note: Keep the key.properties file private; do not check it into public source control.
storePassword=123456
keyPassword=123456
keyAlias=key
storeFile=key.jks
Copy the code

To modify the < app dir > / android/app/build. Gradle (configuration) here are:

def keystoreProperties = new Properties()
def keystorePropertiesFile = rootProject.file('key.properties')
if (keystorePropertiesFile.exists()) {
    keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
}

android {

Copy the code
signingConfigs {
    release {
        keyAlias keystoreProperties['keyAlias']
        keyPassword keystoreProperties['keyPassword']
        storeFile file(keystoreProperties['storeFile'])
        storePassword keystoreProperties['storePassword']
    }
}
buildTypes {
    release {
        signingConfig signingConfigs.release
    }
}
Copy the code

Step3: packaging

Execute the package command:

flutter build apk
Copy the code
You are building a fat APK that includes binaries for android-arm, android-arm64.
If you are deploying the app to the Play Store, it's recommended to use app bundles or split the APK to reduce the APK size. To generate an app bundle, run: flutter build appbundle --target-platform android-arm,android-arm64 Learn more on: https://developer.android.com/guide/app-bundle To split the APKs per ABI, run: flutter build apk --target-platform android-arm,android-arm64 --split-per-abi Learn more on: https://developer.android.com/studio/build/configure-apk-splits#configure-abi-split Initializing gradle... 3.6 s Resolving dependencies... 26.8 S Calling mockable JAR artifact transform to create file: C: \ Users \ \. 15897 gradle \ caches \ \ files transforms - 1-1.1 \ android jar \ e122fbb402658e4e43e8b85a067823c3 \ android jar with input  C:\Users\15897\AppData\Local\Android\Sdk\platforms\android-28\android.jar Running Gradle task 'assembleRelease'...
Running Gradle task 'assembleRelease'... Done 84.7s Built build\app\outputs\apk\release\app-release.apk (3.2mb)Copy the code

Once packaged, the APK file is here build\app\outputs\apk\release\app-release.apk

Packaging related links

  • Flutter website APK packaging tutorial
  • This project packages the configuration codecontrast
  • APK download of this project

More links

  • The source code
  • blog
  • The Denver nuggets