Skip to main content

Overview

The following documentation aims to educate you on building a native mobile application on Flow. It first presents Monster Maker, a starter project we’ve built to represent simple Flow mobile concepts. Next it presents various developer resources related to building mobile native Flow applications.

Monster Maker

monster_maker_logo.png

Monster Maker is a native iOS app that allows users to connect a wallet, sign a transaction to mint an NFT (a monster) and display their collection of NFTs (their monsters) within the app. It’s meant to be a lightweight sample project to exemplify how to build a mobile native Flow project. If you’re looking to build a native mobile application for Flow, exploring the Monster Maker code base first or even building off of it is a great way to get started.

Github Repo

The Monster Maker Github Repo can be found here:

https://github.com/onflow/monster-maker

Building to Device

Before you run Monster Maker on your device, please make sure you have installed the Xcode14 from Mac App Store. Once you clone the repo, open the MonsterMaker.xcodeproj under the iOS folder.

Xcode should automatically setup the project for you. If you do see any error related to dependencies, run Xcode Menu -> File -> Packages -> Reset Package Cache to resolve the issue.

In the meantime, you can choose a simulator or your iPhone to run. For more detail here is the official doc. For run in real device, there are a few steps to deal with signing:

  1. Add your apple account to the Xcode which can be accessed from Xcode Menu -> Settings -> Add account.

  2. Change the Team to your Personal Apple account from the Signing & Capabilities under the project target menu. For more detail, please check the screenshot below.

    XCode Target Setup

Connecting to a Wallet

To connect with wallets, there is native wallet discovery in the app. Once you click on connect, it will bring out the list of the wallets which support HTTP/POST or WC/RPC method.

FCL Config

To make sure, the wallet can recognise your dApp, there is a few field you will need to config before connect to a wallet. The account proof config is optional. In addition, you will need to create a project id from walletconnect cloud before you can connect to the WC/RPC compatible wallet such as dapper self custody or lilico wallet.


_29
import FCL
_29
_29
// Config the App
_29
let defaultProvider: FCL.Provider = .dapperPro
_29
let defaultNetwork: Flow.ChainID = .testnet // or .mainnet
_29
_29
// Optinal: Config for account proof
_29
let accountProof = FCL.Metadata.AccountProofConfig(appIdentifier: "Monster Maker")
_29
_29
// Config for WC/RPC compatible wallet
_29
let walletConnect = FCL.Metadata.WalletConnectConfig(urlScheme: "monster-maker://", projectID: "12ed93a2aae83134c4c8473ca97d9399")
_29
_29
// Config basic dApp info
_29
let metadata = FCL.Metadata(appName: "Monster Maker",
_29
appDescription: "Monster Maker Demo App for mobile",
_29
appIcon: URL(string: "https://i.imgur.com/jscDmDe.png")!,
_29
location: URL(string: "https://monster-maker.vercel.app/")!,
_29
accountProof: accountProof,
_29
walletConnectConfig: walletConnect)
_29
fcl.config(metadata: metadata,
_29
env: defaultNetwork,
_29
provider: defaultProvider)
_29
_29
// Import keywords replacement for cadence query and transaction
_29
fcl.config
_29
.put("0xFungibleToken", value: "0x631e88ae7f1d7c20")
_29
.put("0xMonsterMaker", value: "0xfd3d8fe2c8056370")
_29
.put("0xMetadataViews", value: "0x631e88ae7f1d7c20")
_29
.put("0xTransactionGeneration", value: "0x44051d81c4720882")

Open wallet discovery

In Monster Maker, the Connect button triggers opening of Wallet Discovery

In Monster Maker, the Connect button triggers opening of Wallet Discovery

For the wallet support HTTP/POST, it will use webview to show the following actions.

For the wallet support WC/RPC, it will use deep-link to the wallet for actions.

You can open the native wallet discovery to make the selection, but also you can connect to the specific wallet as well.

Here is the code snippet of it:


_10
import FCL
_10
_10
// Open discovery view
_10
fcl.openDiscovery()
_10
_10
// Or manual connect to specific wallet
_10
try fcl.changeProvider(provider: provider, env: .testnet)
_10
try await fcl.authenticate()

Signing a Transaction

In Monster Maker, Initializing the NFT collection with the Initialize button triggers a transaction.

In Monster Maker, Initializing the NFT collection with the Initialize button triggers a transaction.

Similar to what we have on fcl-js, native sdk also use query and mutate for on-chain interactions. To request a signature from user, you can simply use fcl.mutate method. By default, the user will be the payer, proposer and authorizer, if you want to add custom authorizer please refer to the code from Server and iOS end.


_23
guard let user = fcl.currentUser else {
_23
// Not signin
_23
return
_23
}
_23
_23
let txId = try await fcl.mutate(
_23
cadence: """
_23
transaction(test: String, testInt: Int) {
_23
prepare(signer: AuthAccount) {
_23
log(signer.address)
_23
log(test)
_23
log(testInt)
_23
}
_23
}
_23
""",
_23
args: [
_23
.string("Hello"),
_23
.int(10)
_23
],
_23
gasLimit: 999,
_23
authorizors: [user])
_23
_23
print("txId -> \(txId)")

View NFT

The View page in Monster Maker exemplifies showing Monster Maker NFTs held by the connected wallet

The View page in Monster Maker exemplifies showing Monster Maker NFTs held by the connected wallet

To view the NFT from an wallet address, first and foremost, we highly recommend you use NFT-Catalog standard when you are ready. So that it will be easy to allow other platform like marketplace and wallet to recognise and display your NFT collection. However, during development, you always can query your NFT with fcl.query. Here is an example:

  • Query cadence


    _75
    import NonFungibleToken from 0xNonFungibleToken
    _75
    import MonsterMaker from 0xMonsterMaker
    _75
    import MetadataViews from 0xMetadataViews
    _75
    _75
    access(all) struct Monster {
    _75
    access(all) let name: String
    _75
    access(all) let description: String
    _75
    access(all) let thumbnail: String
    _75
    access(all) let itemID: UInt64
    _75
    access(all) let resourceID: UInt64
    _75
    access(all) let owner: Address
    _75
    access(all) let component: MonsterMaker.MonsterComponent
    _75
    _75
    init(
    _75
    name: String,
    _75
    description: String,
    _75
    thumbnail: String,
    _75
    itemID: UInt64,
    _75
    resourceID: UInt64,
    _75
    owner: Address,
    _75
    component: MonsterMaker.MonsterComponent
    _75
    ) {
    _75
    self.name = name
    _75
    self.description = description
    _75
    self.thumbnail = thumbnail
    _75
    self.itemID = itemID
    _75
    self.resourceID = resourceID
    _75
    self.owner = owner
    _75
    self.component = component
    _75
    }
    _75
    }
    _75
    _75
    access(all) fun getMonsterById(address: Address, itemID: UInt64): Monster? {
    _75
    _75
    if let collection = getAccount(address).getCapability<&MonsterMaker.Collection{NonFungibleToken.CollectionPublic, MonsterMaker.MonsterMakerCollectionPublic}>(MonsterMaker.CollectionPublicPath).borrow() {
    _75
    _75
    if let item = collection.borrowMonsterMaker(id: itemID) {
    _75
    if let view = item.resolveView(Type<MetadataViews.Display>()) {
    _75
    let display = view as! MetadataViews.Display
    _75
    let owner: Address = item.owner!.address!
    _75
    let thumbnail = display.thumbnail as! MetadataViews.HTTPFile
    _75
    _75
    return Monster(
    _75
    name: display.name,
    _75
    description: display.description,
    _75
    thumbnail: thumbnail.url,
    _75
    itemID: itemID,
    _75
    resourceID: item.uuid,
    _75
    owner: address,
    _75
    component: item.component
    _75
    )
    _75
    }
    _75
    }
    _75
    }
    _75
    _75
    return nil
    _75
    }
    _75
    _75
    access(all) fun main(address: Address): [Monster] {
    _75
    let account = getAccount(address)
    _75
    let collectionRef = account.getCapability(MonsterMaker.CollectionPublicPath)!.borrow<&{NonFungibleToken.CollectionPublic}>()
    _75
    ?? panic("Could not borrow capability from public collection")
    _75
    _75
    let ids = collectionRef.getIDs()
    _75
    _75
    let monsters : [Monster] = []
    _75
    _75
    for id in ids {
    _75
    if let monster = getMonsterById(address: address, itemID: id) {
    _75
    monsters.append(monster)
    _75
    }
    _75
    }
    _75
    _75
    return monsters
    _75
    }


_10
let nftList = try await fcl.query(script: cadenceScript,
_10
args: [.address(address)])
_10
.decode([NFTModel].self)

External Resources

FCL Swift

FCL Swift is the iOS native SDK for FCL. This SDK is integrated into the Monster Maker sample.

https://github.com/Outblock/fcl-swift

FCL Android

FCL Android is the Android native SDK for FCL.

https://github.com/Outblock/fcl-android

FCL Wallet Connect 2.0

One of the easiest ways to connect to a wallet via a mobile native dApp is through Flow’s new support for Wallet Connect 2.0. This is the pattern that Monster Maker uses to connect to the Dapper Self Custody wallet and Lilico. For more information on FCL Wallet Connect 2.0, check out this page:

FCL Wallet Connect

How to Build a Native iOS Dapp

The Agile Monkeys has written a very comprehensive guide on how to build a native mobile application on iOS and interface with fcl-swift. Found here:

How to Build a Native iOS Dapper Source Code