Vocdoni Mobile Client .Official implementation of the Vocdoni core features.
Official implementation of the Vocdoni core features.
The app can run in three diferent modes:
Flavors are available on Android. The iOS project uses dev
and production
depending on the XCode target.
The State Management architecture of the app is built on top of the Eventual package. Eventual allows to track updates on objects and rebuild their corresponding UI accordingly.
They can be of the type:
This separation allows for efficient and granular widget tree rebuilds whenever the state is updated. If a single value changes, only the relevant children should rebuild.
Initialize and read Global Model's data in globals.dart
// entitiesPersistence = EntitiesPersistence();
await Global.entitiesPersistence.readAll();
// ...
// entityPool = EntityPoolModel();
await Globals.entityPool.readFromStorage(); // will import and arrange the persisted data
Consume Models in specific places
Typically, a Pool with all the EntityModel's known to the app and then, individual EntityModel
instances when the user selects one.
// Globals.entityPool
// ...
// Widget
@override
Widget build(BuildContext context) {
// From the pool, we grab the first entity model
final myEntity = Globals.entityPool.value.first;
// Consume many values (EventualNotifier) locally
return EventualBuilder(
notifiers: [myEntity.feed, myEntity.processes], // EventualNotifier<T> values that may change over time
builder: (context) {
// rebuilt whenever either of myEntity.feed or myEntity.processes change
// ...
)
);
}
In the example above, updates on specifig Feed items, will not affect the current widget. But as soon as we call myEntity.feed.refresh()
on this instance, the Builder will be triggered because of the changes in isLoading
, hasError
and hasValue
.
Certain models implement the ModelRefreshable
interface. This ensures that callers can call refresh()
to request a refetch of remote data, based on the current model's ID or metadata.
Other models (mainly pools) also implement the ModelPersistable
interface, so that readFromStorage()
and writeToStorage()
can be called.
It is important not to mix the models (account, entity, process, feed, app state) with the Persistence classes. Persistence classes map diretly to dvote-protobuf
classes, which allow for binary serialization and consistently have a 1:1 mapping.
Models can contain both data which is persisted (entity metadata, process metadata) as well as data that is ephemeral (current participants on a vote, selected account). When readFromStorage
is called, data needs to be deserialized and restored properly, often across multiple models.
import 'package:vocdoni/lib/i18n.dart';
on your widget filegetText(context, "My new string to translate")
make lang-extract
assets/i18n/*.json
The app's strings translation can be found on Weblate.
Weblate monitors origin/i18n
and pulls from it as new strings are available. After a new translation is added, Weblate compiles the new JSON files in a git remote of its own.
To pull from it, run make init
. This will add a weblate
git remote besides origin
. This way:
weblate/i18n
has the translated strings to integrate into the apporigin/i18n
contains the empty strings that Weblate will show to translatorsThe translation flow is an iterative loop that looks like:
git checkout i18n
git pull weblate i18n
(update our local repo)git push origin i18n
(update the GitHub branch)git checkout main
(check out the latest code)git merge i18n
(integrate the latest translations)git push origin main
(update the GitHub branch)git checkout i18n
git merge main
(integrate the latest code into i18n)make lang-parse
(extract the new strings)git add assets/i18n/*
(stage the language files for commit)git commit -m "Updated strings"
git push origin i18n
(push the new strings for Weblate)git checkout main
Important:
"action.createIdentity"
instead of "main.createIdentity"
"question.doYouWantToRemoveName"
=> "Do you want to remove {{NAME}}?"
The project makes use of the DVote Flutter plugin. Please, see the repository for more details.
The app accepts Deep Links from the following domains:
app.vocdoni.net
app.dev.vocdoni.net
vocdoni.page.link
vocdonidev.page.link
To enable them:
linking/assetlink.json
on https://app.vocdoni.net/.well-known/assetlinks.json
linking/assetlink.json
on https://app.dev.vocdoni.net/.well-known/assetlinks.json
linking/apple-app-site-association
on https://app.vocdoni.net/.well-known/apple-app-site-association
app.vocdoni.net
and app.dev.vocdoni.net
https://app.vocdoni.net/entities/#/<entity-id>
https://app.vocdoni.net/entities/#/<process-id>
https://app.vocdoni.net/news/#/<process-id>
https://app.vocdoni.net/validation/#/<entity-id>/<validation-token>
The same applies to app.dev.vocdoni.net
vocdoni.page.link
and vocdonidev.page.link
link
query string parameter is extracted, which should contain a link like the ones above from app.vocdoni.net
and app.dev.vocdoni.net
The app also accepts URI's using the vocdoni:
schema, with the same paths and parameters as above:
vocdoni://vocdoni.app/entities/#/<entity-id>
vocdoni://vocdoni.app/processes/#/<entity-id>/<process-id>
vocdoni://vocdoni.app/posts/#/<entity-id>/<idx>
vocdoni://vocdoni.app/validation/#/<entity-id>/<validation-token>
On developoment, you can test it by running make launch-ios-org
or make launch-android-org
The data
field of incoming push notifications is expected to contain three keys:
{
notification: { ... },
data: {
uri: "https://vocdoni.link/processes/0x1234.../0x2345...",
event: "new-process", // enum, see below
message: "..." // same as notification > body
}
}
uri
field will be used the same as a deep link and will determine where the app navigates to.event
can be one of:entity-updated
: The metadata of the entity has changed (but not the process list or the news feed)new-post
: A new post has been added to the Feednew-process
: A process has been createdprocess-ended
: The end block has been reachedmessage
is a copy of the relevant text contained within notification
(not always present)In order to drop unused permissions, edit ios/Podfile
and make sure to uncomment the permissions that are not needed after target.build_configurations.each
.
r_scan
plugin breaks the build on iOS and/or is rejected by Google Playpubspec.yaml
and comment/uncomment the dependencies accordinglyApp.framework
is built for another architecturerm -Rf ios/Flutter/App.framework
and try againAuthor: vocdoni
Source Code: https://github.com/vocdoni/mobile
Google has announced new flutter 1.20 stable with many improvements, and features, enabling flutter for Desktop and Web
Flutter has been booming worldwide from the past few years. While there are many popular mobile app development technologies out there, Flutter has managed to leave its mark in the mobile application development world. In this article, we’ve curated the best Flutter app templates available on the market as of July 2020.
As the new decade dawns upon us, a slew of technologies has been making a lot of noise to grab the developers’ attention. While native app development is going strong, the trade winds are now blowing towards going cross-platform.
This article covers everything about mobile app wireframe design: what to do and what not, tools used in designing a mobile or web app wireframe, and more.
The mobile application scenario has been continually changing over the years. In recent years India has become a center for mobile app development companies. The increase of smartphones has instantly increased the requirements for these apps. Every year new technological trends occur due to contin