Exploring Cross-Platform Development Wit
Exploring Cross-Platform Development Wit
Flutter in Action
by Eric Windmill
ISBN 9781617296147
310 pages
$39.99
Xamarin in Action
by Jim Bennett
ISBN 9781617294389
608 pages
$43.99
React in Motion
by Zac Braddy
Course duration: 3h 51m
$59.99
Many of the designations used by manufacturers and sellers to distinguish their products are
claimed as trademarks. Where those designations appear in the book, and Manning
Publications was aware of a trademark claim, the designations have been printed in initial caps
or all caps.
Recognizing the importance of preserving what has been written, it is Manning’s policy to have
the books we publish printed on acid-free paper, and we exert our best efforts to that end.
Recognizing also our responsibility to conserve the resources of our planet, Manning books
are printed on paper that is at least 15 percent recycled and processed without the use of
elemental chlorine.
ISBN: 9781617296789
Printed in the United States of America
1 2 3 4 5 6 7 8 9 10 - EBM - 24 23 22 21 20 19
iii
iv
I n this chapter, you’ll jump right into building a simple to-do app in React,
learning React concepts as you go.
<View>
<Heading />
<Input />
<TodoList />
<Button />
<TabBar />
</View>
The app will display a heading, a text input, a button, and a tab bar. When you add a
todo, the app will add it to the array of todos and display the new todo beneath the input.
Each todo will have two buttons: Done and Delete. The Done button will mark it as com-
plete, and the Delete button will remove it from the array of todos. At the bottom of the
screen, the tab bar will filter the todos based on whether they’re complete or still active.
Figure 3.1 Todo app design Figure 3.2 Todo app with descriptions
NOTE I’m using React Native version 0.51.0 for this example. Newer versions
may have API changes, but nothing should be broken for building the todo
app. You’re welcome to use the most recent version of React Native, but if you
run into issues, use the exact version I’m using here.
In the index file, import an App component (which you’ll create soon), and delete the
styling along with any extra components you’re no longer using.
Here, you bring in AppRegistry from react-native. You also bring in the main App
component, which you’ll create next.
In the AppRegistry method, you initiate the application. AppRegistry is the JS
entry point to running all React Native apps. It takes two arguments: the appKey, or
the name of the application you defined when you initialized the app; and a function
that returns the React Native component you want to use as the entry point of the
app. In this case, you’re returning the TodoApp component declared in listing 3.2.
Now, create a folder called app in the root of the application. In the app folder,
create a file called App.js and add the basic code shown in the next listing.
You import a new component called ScrollView, which wraps the platform Scroll-
View and is basically a scrollable View component. A keyboardShouldPersistTaps
prop of always is added: this prop will dismiss the keyboard if it’s open and allow the
UI to process any onPress events. You make sure both the ScrollView and the parent
View of the ScrollView have a flex:1 value. flex:1 is a style value that makes the
component fill the entire space of its parent container.
Now, set up an initial state for some of the values you’ll need later. You need an
array to keep your todos, which you’ll name todos; a value to hold the current state of
the TextInput that will add the todos, named inputValue; and a value to store the
type of todo that you’re currently viewing (All, Current, or Active), named type.
In App.js, before the render function, add a constructor and an initial state to the
class, and initialize these values in the state.
...
inputValue: '',
todos: [],
type: 'All'
}
}
render() {
...
}
}
...
Next, create the Heading component and give it some styling. In the app folder, create
a file called Heading.js. This will be a stateless component.
Note that in the styling of headerText, you pass an rgba value to color. If you aren’t
familiar with RGBA, the first three values make up the RGB color values, and the last
value represents the alpha or opacity (red, blue, green, alpha). You pass in an alpha
value of 0.25, or 25%. You also set the font weight to 100, which will give the text a
thinner weight and look.
Go back into App.js, bring in the Heading component, and place it in the Scroll-
View, replacing the empty View you originally placed there.
Run the app to see the new heading and app layout: see figure 3.4. To run the app
in iOS, use react-native run-ios. To run in Android, use react-native run-
android in your terminal from the root of your React Native application.
Next, create the TextInput component and give it some styling. In the app folder, cre-
ate a file called Input.js.
You’re using a new React Native component called TextInput here. If you’re familiar
with web development, this is similar to an HTML input. You also give both the
TextInput and the outer View their own styling.
TextInput takes a few other props. Here, you specify a placeholder to show text
before the user starts to type, a placeholderTextColor that styles the placeholder
text, and a selectionColor that styles the cursor for the TextInput.
The next step, in section 3.4, will be to wire up a function to get the value of the
TextInput and save it to the state of the App component. You’ll also go into App.js and
add a new function called inputChange below the constructor and above the render
function. This function will update the state value of inputValue with the value passed
in, and for now will also log out the value of inputValue for you to make sure the func-
tion is working by using console.log(). But to view console.log() statements in
React Native, you first need to open the developer menu. Let’s see how it works.
NOTE If you aren’t interested in the developer menu or want to skip this sec-
tion for now, go to section 3.4 to continue building the todo app.
NOTE If Cmd-D or Cmd-Ctrl-Z doesn’t open the menu, you may need to con-
nect your hardware to the keyboard. To do this, go to Hardware > Keyboard >
Connect Hardware Keyboard in your simulator menu.
Figure 3.5 Manually opening the developer Figure 3.6 React Native developer
menu (iOS simulator) menu (iOS simulator)
When you do, you should see the developer menu shown in figure 3.8.
Hardware menu
Figure 3.7 Manually opening the Figure 3.8 React Native developer menu
hardware menu (Android emulator) (Android emulator)
Reload (iOS and Android) —Reloads the app. This can also be done by pressing
Cmd-R on the keyboard (iOS) or pressing R twice (Android).
Debug JS Remotely (iOS and Android) —Opens the Chrome dev tools and gives you
full debugging support through the browser (figure 3.9). Here, you have access
not only to logging statements in your code, but also to breakpoints and whatever
you’re used to while debugging web apps (with the exception of the DOM). If you
need to log any information or data in your app, this is usually the place to do so.
Enable Live Reload (iOS and Android)—Enables live reload. When you make
changes in your code, the entire app will reload and refresh in the simulator.
Start Systrace (iOS only)—Systrace is a profiling tool. This will give you a good
idea of where your time is being spent during each 16 ms frame while your app
is running. Profiled code blocks are surrounded by start/end markers that are
then visualized in a colorful chart format. Systrace can also be enabled manu-
ally from the command line in Android. If you want to learn more, check out
the docs for a very comprehensive overview.
Enable Hot Reloading (iOS and Android)—A great feature added in version .22 of
React Native. It offers an amazing developer experience, giving you the ability to
see your changes immediately as files are changed without losing the current state
of the app. This is especially useful for making UI changes deep in your app with-
out losing state. It’s different than live reloading because it retains the current
state of your app, only updating the components and state that have been
changed (live reloading reloads the entire app, therefore losing the current state).
Toggle Inspector (iOS and Android)—Brings up a property inspector similar to
what you see in the Chrome dev tools. You can click an element and see where
it is in the hierarchy of components, as well as any styling applied to the ele-
ment (figure 3.10).
Show Perf Monitor (iOS and Android)—Brings up a small box in the upper-left cor-
ner of the app, giving some information about the app’s performance. Here
you’ll see the amount of RAM being used and the number of frames per second
at which the app is currently running. If you click the box, it will expand to
show even more information (figure 3.11).
Dev Settings (Android emulator only)—Brings up additional debugging options,
including an easy way to toggle between the __DEV__ environment variable
being true or false (figure 3.12).
...
import Heading from './Heading'
inputChange takes one argument, the value of the TextInput, and updates the input-
Value in the state with the returned value from the TextInput.
Now, you need to wire up the function with the TextInput in the Input compo-
nent. Open app/Input.js, and update the TextInput component with the new
inputChange function and the inputValue property.
...
const Input = ({ inputValue, inputChange }) => (
Destructures the inputValue
<View style={styles.inputContainer}>
and inputChange props
<TextInput
value={inputValue}
style={styles.input}
placeholder='What needs to be done?'
placeholderTextColor='#CACACA'
selectionColor='#666666' Sets the onChangeText
onChangeText={inputChange} /> method to inputChange
</View>
)
...
You destructure the props inputValue and inputChange in the creation of the stateless
component. When the value of the TextInput changes, the inputChange function is
called, and the value is passed to the parent component to set the state of inputValue.
You also set the value of the TextInput to be inputValue, so you can later control and
reset the TextInput. onChangeText is a method that will be called every time the value
of the TextInput component is changed and will be passed the value of the TextInput.
Run the project again and see how it looks (figure 3.13). You’re logging the value
of the input, so as you type you should see the value being logged out to the console
(figure 3.14).
Figure 3.14 Logging out the TextInput value with the inputChange method
Now that the value of the inputValue is being stored in the state, you need to create a
button to add the items to a list of todos. Before you do, create a function that you’ll
bind to the button to add the new todo to the array of todos defined in the construc-
tor. Call this function submitTodo, and place it after the inputChange function and
before the render function.
Next, create the todoIndex at the top of the App.js file, below the last import state-
ment.
...
import Input from './Input'
let todoIndex = 0
Now that the submitTodo function has been created, create a file called Button.js and
wire up the function to work with the button.
In this component, you use TouchableHighlight for the first time. TouchableHigh-
light is one of the ways you can create buttons in React Native and is fundamentally
comparable to the HTML button element.
With TouchableHighlight, you can wrap views and make them respond properly
to touch events. On press down, the default backgroundColor is replaced with a speci-
fied underlayColor property that you’ll provide as a prop. Here you specify an under-
layColor of '#efefef', which is a light gray; the background color is white. This will
give the user a good sense of whether the touch event has registered. If no underlay-
Color is defined, it defaults to black.
TouchableHighlight supports only one main child component. Here, you pass in
a Text component. If you want multiple components in a TouchableHighlight, wrap
them in a single View, and pass this View as the child of the TouchableHighlight.
NOTE There’s also quite a bit of styling going on in listing 3.12. Don’t worry
about styling specifics in this chapter: we cover them in depth in chapters 4
and 5. But do look at them, to get an idea how styling works in each compo-
nent. This will help a lot in the in-depth later chapters, because you’ll already
have been exposed to some styling properties and how they work.
You’ve created the Button component and wired it up with the function defined in
App.js. Now bring this component into the app (app/App.js) and see if it works!
...
import Button from './Button'
...
constructor() {
super()
this.state = {
inputValue: '', Binds the method to the class in
todos: [], the constructor. Because you’re
type: 'All' using classes, functions won’t be
} auto-bound to the class.
this.submitTodo = this.submitTodo.bind(this)
}
...
render () {
let { inputValue } = this.state
return (
<View style={styles.container}>
<ScrollView
keyboardShouldPersistTaps='always'
style={styles.content}> Place the Button below the
<Heading /> Input component, and pass in
<Input submitTodo as a prop.
inputValue={inputValue}
inputChange={(text) => this.inputChange(text)} />
<Button submitTodo={this.submitTodo} />
</ScrollView>
</View>
)
}
You import the Button component and place it under the Input component in the
render function. submitTodo is passed in to the Button as a property called
this.submitTodo.
Now, refresh the app. It should look like figure 3.15. When you add a todo, the
TextInput should clear, and the app state should log to the console, showing an
array of todos with the new todo in the array (figure 3.16).
Now that you’re adding todos to the array of todos, you need to render them to the
screen. To get started with this, you need to create two new components: TodoList
and Todo. TodoList will render the list of Todos and will use the Todo component for
each individual todo. Begin by creating a file named Todo.js in the app folder.
The Todo component takes one property for now—a todo—and renders the title in a
Text component. You also add styling to the View and Text components.
Next, create the TodoList component (app/TodoList.js).
)
})
return (
<View>
{todos}
</View>
)
}
The TodoList component takes one property for now: an array of todos. You then
map over these todos and create a new Todo component (imported at the top of the
file) for each todo, passing in the todo as a property to the Todo component. You also
specify a key and pass in the index of the todo item as a key to each component. The
key property helps React identify the items that have changed when the diff with the
virtual DOM is computed. React will give you a warning if you leave this out.
The last thing you need to do is import the TodoList component into the App.js
file and pass in the todos as a property.
...
import TodoList from './TodoList'
...
render () {
const { inputValue, todos } = this.state
return (
<View style={styles.container}>
<ScrollView
keyboardShouldPersistTaps='always'
style={styles.content}>
<Heading />
<Input inputValue={inputValue} inputChange={(text) =>
this.inputChange(text)} />
<TodoList todos={todos} />
<Button submitTodo={this.submitTodo} />
</ScrollView>
</View>
)
}
...
Run the app. When you add a todo, you should see it pop up in the list of todos (fig-
ure 3.17).
The next steps are to mark a todo as complete, and to delete a todo. Open App.js,
and create toggleComplete and deleteTodo functions below the submitTodo func-
tion. toggleComplete will toggle whether the todo is complete, and deleteTodo will
delete the todo.
toggleComplete (todoIndex) {
let todos = this.state.todos toggleComplete also takes the todoIndex as
todos.forEach((todo) => {
an argument, and loops through the todos
if (todo.todoIndex === todoIndex) {
until it finds the todo with the given index.
todo.complete = !todo.complete
} It changes the complete Boolean to the
}) opposite of complete’s current setting, and
this.setState({ todos }) then resets the state of the todos.
}
...
To hook in these functions, you need to create a button component to pass in to the
todo. In the app folder, create a new file called TodoButton.js.
render () {
...
<TodoList
toggleComplete={this.toggleComplete}
deleteTodo={this.deleteTodo}
todos={todos} />
<Button submitTodo={this.submitTodo} />
...
}
...
const TodoList = ({ todos, deleteTodo, toggleComplete }) => {
todos = todos.map((todo, i) => {
return (
<Todo
deleteTodo={deleteTodo}
toggleComplete={toggleComplete}
key={i}
todo={todo} />
)
})
...
Finally, open Todo.js and update the Todo component to bring in the new TodoButton
component and some styling for the button container.
You add two TodoButtons: one named Done, and one named Delete. You also pass
toggleComplete and deleteTodo as functions to be called as the onPress you defined
in TodoButton.js. If you refresh the app and add a todo, you should now see the new
buttons (figure 3.18).
If you click Done, the button text should be bold and green. If you click Delete,
the todo should disappear from the list of todos.
You’re now almost done with the app. The final step is to build a tab bar filter that
will show either all the todos, only the complete todos, or only the incomplete todos.
To get this started, you’ll create a new function that will set the type of todos to show.
In the constructor, you set a state type variable to 'All' when you first created the
app. You’ll now create a function named setType that will take a type as an argument
and update the type in the state. Place this function below the toggle-Complete func-
tion in App.js.
constructor () {
...
this.setType = this.setType.bind(this)
}
...
setType (type) {
this.setState({ type })
}
...
Next, you need to create the TabBar and TabBarItem components. First, create the
TabBar component: add a file in the app folder named TabBar.js.
This component takes two props: setType and type. Both are passed down from the
main App component.
In the TouchableHighlight component, you check a few props and set styles based on
the prop. If selected is true, you give it the style styles.selected. If border is true,
you give it the style styles.border. If type is equal to the title, you give it
styles.selected.
In the Text component, you also check to see whether type is equal to title. If so,
add a bold style to it.
To implement the TabBar, open app/App.js, bring in the TabBar component, and
set it up. You’ll also bring in type to the render function as part of destructuring
this.state.
...
import TabBar from './TabBar'
class App extends Component {
...
render () {
const { todos, inputValue, type } = this.state
return (
<View style={styles.container}>
<ScrollView
keyboardShouldPersistTaps='always'
style={styles.content}>
<Heading />
<Input inputValue={inputValue}
inputChange={(text) => this.inputChange(text)} />
<TodoList
type={type}
toggleComplete={this.toggleComplete}
deleteTodo={this.deleteTodo}
todos={todos} />
<Button submitTodo={this.submitTodo} />
</ScrollView>
<TabBar type={type} setType={this.setType} />
</View>
)
}
...
Here, you bring in the TabBar component. You then destructure type from the state
and pass it not only to the new TabBar component, but also to the TodoList component; you’ll
use this type variable in just a second when filtering the todos based on this type. You
also pass the setType function as a prop to the TabBar component.
The last thing you need to do is open the TodoList component and add a filter to
return only the todos of the type you currently want back, based on the tab that’s
selected. Open TodoList.js, destructure the type out of the props, and add the follow-
ing getVisibleTodos function before the return statement.
...
const TodoList = ({ todos, deleteTodo, toggleComplete, type }) => {
const getVisibleTodos = (todos, type) => {
switch (type) {
case 'All':
return todos
case 'Complete':
return todos.filter((t) => t.complete)
case 'Active':
return todos.filter((t) => !t.complete)
}
}
You use a switch statement to check which type is currently set. If 'All' is set, you
return the entire list of todos. If 'Complete' is set, you filter the todos and only return
the complete todos. If 'Active' is set, you filter the todos and only return the incom-
plete todos.
You then set the todos variable as the returned value of getVisibleTodos. Now
you should be able to run the app and see the new TabBar (figure 3.19). The TabBar
will filter based on which type is selected.
Summary
AppRegistry is the JavaScript entry point to running all React Native apps.
The React Native component TextInput is similar to an HTML input. You can
specify several props, including a placeholder to show text before the user
starts to type, a placeholderTextColor that styles the placeholder text, and a
selection-Color that styles the cursor for the TextInput.
TouchableHighlight is one way to create buttons in React Native; it’s compara-
ble to the HTML button element. You can use TouchableHighlight to wrap
views and make them respond properly to touch events.
You learned how to enable the developer tools in both iOS and Android emula-
tors.
Using the JavaScript console (available from the developer menu) is a good way
to debug your app and log useful information.
Hello MVVM—creating
a simple cross-platform
app using MVVM
Typically at this point in a book, it’s traditional to build a Hello World app to show off
the technology in question. For this book, though, I’m going to go slightly against tra-
dition and start by discussing the MVVM (model-view–view model) design pattern.
Then we’ll get our hands dirty with some code toward the end of this chapter.
31
First, this isn’t easily testable. You can only test this iOS Android
app by updating the value in the text box and tapping Application C# C#
the button. It would be better if you could write unit layer
Figure 2.4 MVVM has a model, a view model, a view, and a binding layer that keeps the view
and view model in sync and connects events on the view to the view model.
Reflecting on reflection
If you’ve never heard of reflection before, it’s a part of the C# API that allows you to
query details about a class—you can discover properties, fields, methods, or events.
Once you’ve found out the details, you can also execute code. For example, you can
find a property based on its name and then get the value of that property from a par-
ticular instance of that class. Reflection is also common in other languages such as
Java—C# reflection is basically the same as Java reflection.
This is great for binding—if you bind a property called Name, the binding code can use
reflection to find a property on your view-model class with that same name, and then
it can get the value on your view-model instance.
For our calculator app, the binding would wire up the text box, button, and label on
the UI to the equivalent properties and a command on the view model.
There’s a bit of magic involved in making this binder work, and this is usually
implemented in an MVVM framework—a third-party library that gives you a set of
base classes providing the implementation of this pattern. I cover how this works later
in this chapter.
MVVM FRAMEWORKS There are multiple MVVM frameworks that work with
Xamarin native apps, such as MvvmCross, MVVM Light, and Caliburn.Micro.
Although each one has differences, they all follow the same basic principles
and do roughly the same things. Later in this book we’ll be using MvvmCross,
but everything in this book is applicable to most frameworks.
For example, as shown in figure 2.5, we could have a text box on our calculator app
UI that’s bound to a Number property. This means that at runtime it will try to find a
public property called Number on the view model that it’s bound to using reflection,
Sqrt
The Text property of the TextBox is bound to a property
called “Number” on the view model. The binding looks
up the “Number” property on the view model and finds
400 it using reflection.
Figure 2.5 Binding keeps the value on the view in sync with the value in the view model.
and it will show the string contained in that property in the text box. If the user
changes the value inside the text box, it will update the value of the Number property
to match what the user has typed in. Conversely, if the value of the Number property on
the view model changes, the binding will update the text box to match.
The binder doesn’t care what the underlying class type is of the view model you’re
using, just that it has a public property called Number that it can extract the value from.
In some of the MVVM frameworks, it doesn’t even care if the property is there or not. If
it can’t find the property, it just treats it as an empty value. This loose coupling is what
makes MVVM especially powerful—it allows view models to be completely agnostic to
the view, so you can write unit tests against the view model that simulate the UI without
worrying about UI code getting in the way. It also supports code reuse, so a view could
be glued to any view model that has properties with the names it’s expecting.
Figure 2.6 expands on the previous figures by showing how these layers map to the
three layers of MVVM:
App layer—The app layer is one that doesn’t really come under the pure MVVM
design pattern, but the different MVVM frameworks do provide some application-
layer features. This allows us to have some cross-platform code in our app layer
that can control app logic, such as which view is shown first and how the differ-
ent classes in the app are wired together, such as defining which view model is
used for each view.
UI layer—The UI layer is our view layer, and this is platform-specific code.
Binding—The binding between the UI layer and the UI logic layer is the
binder—the glue that connects the UI layer to its logic layer. This is usually
A QUICK HISTORY LESSON MVVM has been around since 2005 and was devel-
oped by two architects from Microsoft, Ken Cooper and Ted Peters. It was pri-
marily created for use with the new UI technology stack coming out of
Microsoft called WPF, and it leverages the data binding that was a key feature
of WPF. In WPF you write your UI using XAML, a UI-based markup language,
and in this XAML you can bind the properties of a UI widget to properties
defined in the data context of the view—essentially the view model. This
allowed UI/UX experts to design the UI using more designer-based tools,
and to simply wire the widgets, based on their names, to code written inde-
pendently by developers.
behind this is simple—each version has more APIs available than the previous version,
and your code can use libraries that target the same or an earlier version of the
standard. For example, if your code targets .NET Standard 1.6, you can use a library
that targets 1.4. You can think of the .NET Framework on Windows as the most com-
plete set of APIs, and each .NET Standard version as a more complete implementa-
tion of the full .NET Framework.
You can read more on .NET Standard libraries, and see what version of the stan-
dard is implement by which version of each platform on Microsoft Docs at
http://mng.bz/sB0y. At the time of writing, Xamarin iOS and Android supports ver-
sion 2.0, so you can use code that targets 2.0 or earlier from your Xamarin apps. Be
aware, though, that targeting higher versions may limit the platforms you support. At
the time of writing, UWP only supports 1.4, so if you decide to add a UWP project to
your Xamarin apps to support Windows 10, you’ll need to ensure the core projects
used by your app target 1.4 or lower.
These .NET Standard libraries are perfect for the cross-platform layer in your
Xamarin apps. The set of APIs that .NET Standard libraries implement includes all
the bits that would work on all platforms—collections, Tasks, simple I/O, and net-
working. What isn’t included is anything that’s specific to a platform, such as UI code.
This is left up to platform-specific code to implement. .NET Standard is just an API
specification, it’s not the actual implementation. Under the hood, the code that
makes up the subset of the .NET APIs isn’t the same on all platforms, each platform
implements their features using the native API that the platform provides. But the
interface to it—the classes and namespaces—are the same.
When you write your cross-platform app,
you want as much code as possible inside iOS Android
Figure 2.8 A typical cross-platform app would contain a .NET Standard library with the core code,
an Android app with Android-specific code, and an iOS app with iOS-specific code
Now that you’ve seen some of the basics, let’s build a simple example app using the
MvvmCross MVVM framework.
Despite using MvvmCross here and in the apps we’ll build in later chapters, the aim is
not to lock you into this framework. We’ll only be using some small parts of it, and the
principles behind those parts are pretty standard for the MVVM pattern. These princi-
ples are easy to apply when using other frameworks, such as MVVM Light.
(continued)
If you get stuck, Xamarin has great documentation on its website (https://aka
.ms/XamDocs) covering everything you need for installation and setup. The site also
has helpful forums with a great community of users, and Xamarin’s own engineers if
you get a particularly strange problem. And obviously, there’s always Stack Overflow.
At this point I’m going to assume you already have everything you need installed. If
not, now would be a good time to do it.
For this little test app, we’re only going to test on the Android emulator and iOS
simulator, so don’t worry if you don’t have a physical device to use. If you do have a
physical device, then put it to one side for now and just use the emulator/simulator as
there’s a bit of device setup you need to do to run and debug apps on real devices. On
Android this is simple, but on iOS it’s a bit more complicated. We’ll be discussing this
in chapter 13.
As previously mentioned, we’ll be using the MvvmCross framework, and luckily for
us there’s an extension available for Visual Studio that allows us to create a new cross-
platform solution. This solution contains a core library and platform-specific apps for
all supported platforms (so on Visual Studio for Mac you get an iOS app and an
Android app; on Windows it’s iOS, Android, WPF, and UWP). Seeing as we’ll be
installing an extension, and the projects we create will need NuGet packages, you’ll
need internet access. This may sound obvious, but if you’re in a coffee shop, now
would be a good time to grab their WiFi password.
The MvvmCross Template Visual Studio has multiple repositories Enter text in here
Pack is under IDE covering stable versions of extensions to search the
extensions in the tree. as well as alpha and beta versions. extension gallery.
You can install extensions from files instead Click Install to install the extension.
of from the repository if needed.
Figure 2.9 Selecting the MvvmCross Template Pack from the Visual Studio extension manager
Once this is installed, it’s a good idea to restart Visual Studio, as the new solution type
won’t appear in the right place until you do.
Once Visual Studio is restarted, you can start creating a new solution. You can
access the New Solution dialog box in three ways.
From the menu by going to File > New > Solution
Using the keyboard shortcut Shift-Command-N (++N)
By clicking the New Project button at the bottom of the Get Started page shown
when you open Visual Studio for the first time. Whichever way you choose,
you’ll then be presented with the New Solution dialog box (figure 2.10).
Select Other >.Net, then select Enter the project name here.
MvvmCross Single Page Native Application By default, the solution is given
from the MvvmCross section. the same name as the project.
Visual Studio will, by default, create all the files You can change the folder the
needed to push this to a Git repository, even project is created in here.
creating an appropriate .gitignore file for you.
Figure 2.10 The New Solution dialog boxes showing the MvvmCross cross-platform app
solution template, and setting the project name
From this dialog box select Other > .NET from the left-side list, and then select Mvvm-
Cross Single Page Native Application from the list in the middle. Click Next. On the
next screen enter HelloCrossPlatformWorld as the project name and click Create.
This will create a new solution for you containing three projects: a .NET Standard
core project (HelloCrossPlatformWorld.Core), an iOS app (HelloCrossPlatform-
World.iOS), and an Android app (HelloCrossPlatformWorld.Droid), as shown in fig-
ure 2.11. Once the solution has been created, it will try to download all the NuGet
packages it needs—you’ll see the status bar at the top showing Adding Packages. This
may take a while, depending on the speed of your internet connection, and you may
be asked to agree to some license agreements as they download. You’ll need to let
them fully download before building the apps.
Installed shows you the extensions Click Download to download Type here to search
you already have installed. and install the extension. the extension gallery.
From the New Project dialog box (shown in figure 2.13), select the MvvmCross section
under Visual C# on the left, choose MvvmCross Single Page Native Application from
the list in the middle, enter HelloCrossPlatformWorld as the project name, and click
OK. Windows has problems with paths longer than 256 characters, and some of the
directories that will be created when your app is built have long names, so you may
want to ensure your solution is created in a folder close to the root of a drive. If you do
it in C:\Users\<username>\Documents\visual studio 2017\Projects, your path may be
too long.
This will create five projects for you: a .NET Standard core project, an iOS app, an
Android app, and a couple of Windows apps covering WPF and UWP. We’re only
interested in supporting iOS and Android here, so you can delete the Universal Win-
dows and WPF projects by selecting them and pressing Delete or using Remove from
the right-click context menu. This will leave you with the same three projects as on
Visual Studio for Mac: the core project, the iOS app, and the Android app, as shown
in figure 2.14.
Select Templates > Visual C# > MvvmCross, If you don’t want to select a template
then select MvvmCross Single Page from the tree, you can type “MvvmCross”
Native Application. in here to quickly find it.
Enter the project name. By You can change the Visual Studio will, by default, create
default, the solution is given folder the project is all the files needed to push this to a
the same name as the project, created in here. Git repository, even creating an
but you can change the solution appropriate .gitignore file for you.
name if you want.
Figure 2.13 The New Project dialog box, where you can create your new solution
When you create this project, the NuGet packages may not be the latest
NuGet packages are versioned. You can install version 1.0 of a package from the
public NuGet server, and later the author could update it to version 1.1. You can then
easily update the NuGet package from inside Visual Studio.
Be wary though. Sometimes packages may not be backwards compatible. The Mvvm-
Cross extension may not always install the latest versions of the MvvmCross NuGet
packages, and if you update them, the code created by the extension will probably
still work, but there are no guarantees.
The core project is a combination of two of our layers—the cross-platform business logic
layer and the cross-platform UI logic layer. These layers don’t need to exist in separate
projects—they’re just conceptual layers. The core contains a view model for the app
plus some cross-platform application logic (we’ll discuss the application layer in the
next chapter). Figure 2.15 shows the structure of this project in the solution pad.
You’ll notice here that we don’t have any models. In this simple example, the
model is just a string that’s wrapped up inside the view model (and we’ll play with
this string a bit later). This isn’t normal—in a real-world case, the view model would
need something in the model layer so that it could represent the model layer’s state
and behavior. For now though, as this is a trivial Hello World, there’s no model layer.
The platform-specific app and view layers, as well as the binding, live inside the
two app projects—one for iOS and one for Android—as the code for these apps is
Figure 2.16 The structure of the iOS and Android app projects
Figure 2.17 Our Hello Cross-Platform World apps running on both Android and iOS
When you used the MvvmCross extension to create the solution, it created these two
apps for you, both using some shared common code.
ANDROID
Let’s start by taking the Android app for a spin.
SWITCHING FROM MAC TO WINDOWS The project and solution files created by
Visual Studio for Mac are fully compatible with Visual Studio on Windows,
and vice versa. This means if you use one tool and want to change to the
other, you can. It also means you can load anyone else’s solution, regardless
of what tools were used to create it.
The first thing to do is to ensure the Android app is the startup project, so right-click
it and select Set as Startup Project. Once this is selected, you’ll see options for choos-
ing the device to run it on in the menu.
On Visual Studio for Mac (on the left in figure 2.18), you’ll see two drop-down
menus in the top left, and from the second one you can choose the device to run on—
an emulator or a physical device (if you have one plugged in). Visual Studio uses the
emulators from Google and installs and configures two of these by default. You should
select the Accelerated x86 emulator, as this will be faster on a Mac; ARM-based emula-
tors run about 10 times slower than the x86 version.
Visual Studio for Windows installs the Visual Studio Emulator for Android as part
of its installer (assuming the option was ticked when you ran the installer), and it will
configure a few of these inside Visual Studio for you to use.
These emulators come in different hardware types and different Android OS ver-
sions. You’ll need to use an x86-based emulator (it’s much faster than the ARM ver-
sion), and all the x86 emulators are basically the same in terms of hardware, just using
a different version of the Android OS. For now, just choose the latest OS version, and
run the app either by clicking the Run button on the toolbar, or by choosing Run >
Start Debugging on Visual Studio for Mac or Debug > Start Debugging on Windows.
Sit back and relax as your app is built and the emulator is launched.
Be aware that the first time your app builds, it will take a very long time—there are
a number of SDK files that Xamarin needs to download in order to build an Android
app, and it downloads these the first time your app is built with no feedback in the
output except that it’s building. Don’t kill the build—if you do, you may have to man-
ually clean up half-downloaded zip files. If you do get errors about corrupt zip files,
you can find information on how to fix them in Xamarin’s Android Troubleshooting
guide at http://mng.bz/MKSQ.
DON’T RUN MORE THAN ONE ANDROID EMULATOR Android emulators can be a
bit fussy sometimes, as they run inside virtual machines. If you try to run more
than one, they can freeze up and not start. If you ever get this happening—
the emulator screen stays black and nothing happens—quit it and close all
other emulators you have running, and try again.
This app doesn’t do much. It just shows off the very basic features of MvvmCross. If
you change the text in the text box, the label below will update to reflect this. We’ll
dive into what’s happening a bit more later, but for now you’re over the first hurdle—
you have an app that runs. Let’s crack on with iOS.
IOS
Building and running the iOS app is very similar to Android. First, ensure the iOS app
is the startup project, just as you did for the Android app.
Next you need to select the device to run on. This is slightly different from
Android. Android always builds the same code for emulators and physical devices, so
all you need to do is choose the device. On Visual Studio for Mac, this is the same—
from the drop-down menu choose a simulator or a physical device if one is available
(on the left in figure 2.19). From here, select the iPhone simulator of your choice,
though a recent one is always good.
Visual Studio for Windows is similar, though it breaks this out into two drop-down
menus—one to choose either a physical device or a simulator, and another that shows
the available devices or simulators (on the right in figure 2.19). In this case, choose
iPhoneSimulator from the first menu, and select the simulator of your choice from
the second.
Once the appropriate simulator is selected, run the app. If you’re using Visual Stu-
dio for Mac, the simulator will run on your Mac. If you’re using Windows, the simula-
tor will either launch on your Mac, or on your Windows PC if you have the iOS
simulator for Windows installed.
Once the simulator fires up, you’ll see the basic MvvmCross sample app. This is
identical to the Android app—edit the text and the label updates to match. Awe-
some—your Xamarin app is running on iOS without any extra work.
Figure 2.20 The structure of the core project showing the location of the FirstViewModel class
This is a one-line code change in one file in shared code. If you build and run the
Android and iOS apps now, you’ll see that both have the new text showing in the text
box and label, as in figure 2.21.
The apps look the same and work the same. The only difference is the original
string value that’s shown on startup.
Figure 2.21 Both sample apps showing the new text, changed by changing only one line
of code
So how does this all work? Let’s look at this solution to see how it fits into our layers.
This app has two views, one on iOS and one on Android, a view model in shared cross-
platform code, and a string that acts as a model (figure 2.22).
Before we can go into much more detail about what’s happening here, there’s a lot
more about MVVM we need to discuss. In the next chapter we’ll take that deeper dive
into MVVM, and once you’ve seen in more depth how MVVM works we’ll look in
more detail at the code we’ve just built.
iOS Android
App C# C#
layer
C# (.NET Standard) App
C# C#
FirstView FirstView
UI layer View
UILabel TextView
UITextField EditText
C# (.NET FirstViewModel
UI logic Standard) View
layer string Hello{get;set;} model
Summary
In this chapter you learned that
A number of design patterns have evolved over time to help in making better
UI applications. The latest incarnation of these, MVVM, is very well suited to
building Xamarin apps, as it maximizes the amount of cross-platform code in
our apps.
A cross-platform Xamarin app is not totally cross-platform. Instead it’s an app
where all platforms are written in the same language (C#) so that you can share
a large portion of your code.
Cross-platform code is written in .NET Standard libraries that provide access to
a subset of .NET that works on all platforms.
The MVVM pattern consists of three layers. You can write two of these layers,
the model and the view model, once inside a .NET Standard library and share
the code between your iOS and Android apps.
You also learned how to
Use an extension in Visual Studio to create a cross-platform Xamarin app, with
projects for iOS and Android, and a .NET Standard library core project for
shared code.
Run these apps inside the iOS simulator and Android emulator.
I n this chapter, you’ll start to build a Flutter app with a focus on the (UI)
user interface. You’ll explore the elements that help you make your Flutter
apps beautiful including layout, themes, styling, and Flutter’s built-in
widgets.
Flutter is more than a framework; it’s also a complete SDK. And perhaps the most
exciting piece of this SDK to me, as a web developer, is the massive library of built-
in widgets that make building the front end of your mobile app easy.
This chapter is all about the user interface and making an app beautiful. It
includes exploring some of the widgets built into Flutter, as well as layout, styling,
and more.
Figure 4.1 shows the app I’ll use to explain the UI in Flutter.
57
1
You can find all the widgets and their descriptions in the official Widget Catalog: https://flutter.dev/docs
/development/ui/widgets
2
The repository is at https://github.com/ericwindmill/flutter_in_action_public, or you can download the
source code from Manning’s website.
Along with declaring assets and importing libraries, this information is all you need
for your Flutter pubspec file.
TIP If you’re writing an app that’s heavy on iOS-style widgets, you can include
an additional flag to import iOS icons.
Along with the pubspec file, your app must have an entry point: a file that includes a
main function. In Flutter apps, the entry point is, by convention, a file called main.dart.
Check out weather_app/main.dart. The main() function runs the app, as in every
Dart program, but it’s also useful for setting up some configuration for the app before
the app runs.
4.1.2 SystemChrome
SystemChrome is a Flutter class that exposes some easy methods to control how your
app displays on the native platform. This class is one of the only classes you’ll use to
manipulate the phone itself (unless you’re writing plug-ins, which are outside the
scope of this chapter.)
In this app, I’m using SystemChrome.setPreferredOrientations to restrict the
app to portrait mode. This class also exposes methods that control what the phone’s
overlays look like. If you have a light-colored app, for example, you can ensure that
the time and battery icons on your phone’s status bar are dark (and vice versa):
void main() {
AppSettings settings = new AppSettings();
The SystemChrome class is something you’ll set once and then forget. I’m showing it to
you up front so that you’re aware of it, but there’s no need to spend too much time on
it. If you’re curious, you can find more on it here: https://api.flutter.dev/flutter/ser-
vices/SystemChrome-class.html.
Before moving on, I need to address the then function used in that code.
EXAMPLE 1. JUST IN TIME: DART FUTURES
You won’t get far into Dart without seeing some async methods here and there.
A Future is the foundational class of all async programming in Dart.
Futures are a lot like receipts at a burger quick-serve restaurant. You, the burger
orderer, tell the employee that you want a burger. The server at the restaurant says,
“Okay, here’s a receipt. This receipt guarantees that sometime in the future, I will give
you a burger as soon as it’s ready.”
So you, the caller, wait until the employee calls your number and then delivers on
the guarantee of a burger. The receipt is the Future. It’s a guarantee that a value will
exists but isn’t quite ready.
Futures are then-able, so when you call a Future, you can always say myFuture-
Method().then((returnValue) ⇒ … do some code … );
Future.then takes a callback, which is executed when the Future value resolves.
In the burger restaurant, the callback is what you decide to do with the burger when
you get it (such as eat it). The value passed into the callback is the return value of the
original Future:
prepareBurger is likely going to take
Future<Burger> orderBurgerFromServer() async { time (for the burger to cook). When it’s
var burger = await prepareBurger(); done being prepared, return it.
return burger;
}
// weather_app/main.dart
void main() { (1)
AppSettings settings = new AppSettings();
The entry
point of // Don't allow landscape mode
your app. SystemChrome.setPreferredOrientations(
[DeviceOrientation.portraitUp, DeviceOrientation.portraitDown])
.then((_) => runApp(MyApp(settings: settings)));
}
runApp is being
passed MyApp, the root
class MyApp extends StatelessWidget {
of your Flutter app.
final AppSettings settings;
MyApp is a
widget, like
everything else.
@override
Widget build(BuildContext context) { The build method of MyApp returns
// ... a MaterialApp as the top-level app.
return MaterialApp( (4)
title: 'Weather App',
debugShowCheckedModeBanner: false,
theme: theme,
home: PageContainer(settings: settings),
);
}
}
Again, this is standard in Flutter apps. Your top-level widget is one that you write your-
self—in this case, MyApp. That widget turns around and uses MaterialApp in i\s build
method, providing a widget in your app in which you can do additional setup.
Looking at that build method again, the following arguments are being passed to
MaterialApp.
//
@override
This flag removes a banner that is shown when
Return a you’re developing your app and running it locally.
MaterialApp. Widget build(BuildContext context) { I turned it off only so the screenshots in this book
// ...
would be cleaner.
return MaterialApp( (1)
One of the aspects title: 'Weather App',
of your app that debugShowCheckedModeBanner: false,
MaterialApp theme: theme, (3)
takes care of for home: PageContainer(settings: settings),
you is the appwide );
Theme (covered } home represents the home page of your app. It can
shortly). be a widget. PageContainer is a widget written
for the weather app that will be covered later.
4.2.2 Scaffold
Like the MaterialApp widget, a Scaffold is a convenience widget that’s designed to
make applications that follow Material guidelines as easy as possible to build. The
MaterialApp widget provides configuration and functionality to your app. Scaffold is
the widget that gives your app structure. You can think of the MaterialApp widget as
being the plumbing and electricity of your app, whereas Scaffold is the foundation
and beams that give your app structure.
Like the MaterialApp, a Scaffold provides functionality that you’d otherwise have
to write yourself. Again, even if you have a highly customized design style that’s not
Material at all, you’ll want to use Scaffold.
Per the Flutter docs, a Scaffold defines the basic Material design visual layout,
which means that it can make your app look like figure 4.2 pretty easily.
Scaffold provides functionality for adding a drawer (an element that animates in
from one of the sides and is commonly used for menus) and a bottom sheet, which is
an element that animates into view from the bottom of the screen and is common in
iOS-style apps. And unless you configure it otherwise, the AppBar in a Scaffold is auto-
matically set up to display a menu button in the top-left corner of your app, which will
open the drawer; when you aren’t on a screen that has a menu, it changes that menu but-
ton to a back button. Those buttons are already wired up and work as expected on tap.
It’s important to know, though, that you can choose the features you want and the
ones you don’t. If your app doesn’t have a drawer-style menu, you can simply not pass
it a drawer, and those automatic menu buttons disappear.
The Scaffold widget provides many optional features, all of which you configure
from the constructor. Here’s the constructor method for the Scaffold class:
I wanted to show this code so you can see that none of these properties is marked as
@required. You can use an AppBar, but you don’t have to. The same is true of drawers,
navigation bars, and so on. For this app, I used only the AppBar. The point is (again)
that even if you’re building an app that you don’t want to look Material, the Scaf-
fold widget is valuable, and I recommend using it.
In the weather app, you can see the Scaffold in the ForecastPage widget.3 The
part I want to point out right now is at the bottom of the file: the return statement of
the ForecastPageState.build method. I only want to show you that the Scaffold is a
widget, and like many widgets, most of its arguments are optional, making it highly
customizable.
You can pass a widget to the appBar
argument, and that widget is placed
// weather_app/lib/page/forecast_page.dart at the top of the screen of your app.
return Scaffold( (AppBar and PreferredSize are
appBar: // ... preferred sized widget covered in the next section.)
body: // ... gesture detector
body is the argument on the Scaffold that represents the the main portion of the
screen. If there’s no appBar, the body is the entire screen for all intents and purposes.
Recall the Scaffold constructor method that I showed earlier, which had more than
ten named arguments. Here, I’m using only two. The point is that these widgets give
you a lot but are highly customizable. In the next section, I give concrete examples of
how the Scaffold is used in the weather app.
3
The ForecastPage is in the directory at weather_app/lib/page/forecast_page.dart.
The property that handles these menu buttons and back buttons is called the leading
action; it can be configured with the AppBar.leading property and the AppBar.auto-
maticallyImplyLeading property. Suppose that you don’t want that menu button to
appear. You can set AppBar.automaticallyImplyLeading to false and then pass the
leading argument to whatever widget you want. This argument attempts to place that
widget on the far-left side of the AppBar (figure 4.4).
Use the PreferredSize widget to use any arbitrary widget in the Scaffold.appBar
property. This approach works because the AppBar widget extends PreferredSize, and
the Scaffold.appBar expects a PreferredSize rather than an AppBar specifically.
preferredSize: Size.fromHeight(ui.appBarHeight(context)),
The second child: ...
required ),
argument is PreferredSize needs two bits of information as
), arguments. The first is its preferredSize, which takes
its child.
a Size class. The Size class defines a height and width.
Now that I’ve talked about PreferredSize at a high-level, I’ll use the weather app for
a concrete example. The point of using a PreferredSize for the app is that the built-
in AppBar widget doesn’t provide a way to animate its colors by default. If you’ve
poked around the app, you may notice that the colors change as the time of day
changes, which required a custom widget called TransitionAppBar. I’ve wrapped it in
a PreferredSize so that the Scaffold accepts it in the appBar argument.
textTheme: Theme.of(context).textTheme.apply(
bodyColor: AppColor.textColorDark,
displayColor: AppColor.textColorDark,
),
);
The other case in which you’d want to use the ThemeData is when you want to set a
style property explicitly. You may want set a container’s background to be the accent
Color of the Theme, for example. Anywhere in your app, you can grab that Theme data
thanks to BuildContext.
BuildContext provides information about a widget’s place in the widget tree,
including information about certain widgets that are higher in the tree, such as Theme.
If you want to know the accent Color of the theme for any given widget, you can say
“Hey, BuildContext, what’s the accent color assigned to the ThemeData that’s closest
up the tree from this widget?” In the next section, I explain that sentence further and
make it less abstract.
NOTE You can create widgets that have their own of method, and you can
access the state of those widgets anywhere in the tree. For now, all that mat-
ters is that certain built-in widgets can be accessed anywhere in your app.
As mentioned, the MediaQuery class is great for getting size information about the
entire screen on which your app is rendered. You access that information by calling
the static method MediaQuery.of(context).size. This method returns a a Size
object with the device’s width and height. Let me break that down a bit more.
Because of is a static method, you call it directly on the MediaQuery class rather than
on an instance of the class. Also, the of method can provide the MediaQuery class only if
it knows the BuildContext in which of is called. That’s why you pass it context. Finally,
size is a getter on the MediaQuery class that represents the device’s width and height.
Look at this example of using MediaQuery. After grabbing the information, you
can use it to determine the size of a widget, based on the screen size. To get 80 per-
cent of the width of the phone, for example, you could write
var width = MediaQuery.of(context).size.width * 0.8;
Again, a widget’s build context gives Flutter a reference to that widget’s place in the
tree. The of method, which always takes a context regardless of which object it’s
defined on, says “Hey, Flutter, give me a reference to the nearest widget of this type in
the tree above myself.”
MediaQuery is the first place you should look if you’re trying to get specific infor-
mation about the physical device your app is running on or if you want to manipulate
the device. You can use it to
Ask whether the phone is in portrait or landscape orientation.
Disable animations and invert colors for accessibility reasons.
Ask the phone whether the user has the text-size factor scaled up.
Set the padding for your entire app.
In the weather app, I use MediaQuery to ensure that widgets are scaled to the proper
sizes based on the size of the screen. The following section shows an example.
The method Size.fromHeight is a constructor on the Size class that creates a Size
object with the given height and an infinite width. That leaves the ui.appBarHeight
method.
NOTE ui is a package alias for a local file. It’s imported as import 'pack-
age:weather_app/utils/flutter_ui_utils.dart' as ui; at the top of the
forecast_page.dart file.
NOTE The built-in AppBar widget is smart itself, but you need a custom wid-
get because you’ll eventually add custom style and animation to it.
This bit of code is going to tell the Scaffold (the preferred size’s parent) how big the
AppBar wants to be. Specifically, it tells Flutter to create a Size instance from a height
that’s appropriate for any screen.
This example is specific, to be sure. The appBarHeight method is useful only for the
AppBar. The screenAwareSize method could be reused. In any case, the point is to
show off the MediaQuery widget, which you’ll likely use quite a bit for styling and layout.
For now, that’s it for the MediaQuery class.
The sun, the clouds and the content are different widgets stacked on top of one
another. All the children of a Stack are positioned or (by default) nonpositioned. Before
I talk about the idea of being positioned, it’s important to understand the Stack’s
default behavior. The widget treats nonpositioned children the same way that a col-
umn or row treats its children. It aligns its children widgets by their top-left corners
and lays them out one after the other next to each other. You can tell the widget which
direction to align in with the alignment property. If you set the alignment to hori-
zontal, for example, the Stack behaves like a row.
In other words, a Stack could work exactly like a column, laying its children out
vertically unless you explicitly make a child positioned, in which case it’s removed
from the layout flow and placed where you tell it to be.
In the weather app, you use a Stack in the ForecastPage. In the Scaffold.body prop-
erty, which has three children, the children are the content of the ForecastPage,
which looks like this:
Column(
children: <Widget>[
forecastContent,
mainContent, This last widget represents
Flexible(child: timePickerRow), all the content that’s on the
] topmost layer of the stack.
),
],
),
For the sake of example, the app could look like this if it weren’t animated:
Stack(
children: <Widget>[
Positioned(
left: 100.0,
top: 100.0,
child: Sun(...),
),
Positioned(
left: 110.0,
top: 110.0,
child: Clouds(...),
),
Column(
children: <Widget>[
forecastContent,
mainContent,
Flexible(child: timePickerRow),
]
),
],
),
Stack is your go-to widget if you want to place widgets either on top of each other, or
in an explicit way in relationship to each other.
Figure 4.7 Screenshot of the Table widget in the context of the weather app
advance, and no table cell can be empty. Given these rules, you implement a table in
code similar to that of other multichild widgets. The simple version API looks like this:
With all that in mind, if you pass in some children, all the columns have the same
width because they’re all flexed, sizing themselves in relationship to one other. The ele-
ments in a row work together to take up the full width. I’ve configured the Table wid-
get that displays in the ForecastPage to be spaced as shown in figure 4.8. (The dotted
lines are added for the example and not in the code.)
Figure 4.8 Table diagram with borders to show rows and columns
Following is the code snippet that defines the sizes of some rows. This code is import-
ant! Notice that there’s no definition for the width of column one.
The look I wanted required column 1 (in a 0-based column count, so it’s the second
column visually) to take up as much space as possible while the rest are fixed. Because
the defaultColumnWidth defaults to being flexed, you don’t need to give it a width.
The remaining piece of the puzzle is a TableRow, which is a bit simpler than a normal
row. Keep in mind two important configurations:
Every row in a table must have an equal number of children.
You can, but don’t have to, use TableCell in the children’s subwidget trees.
The TableCell doesn’t have to be a direct child of the TableRow, either, as long
as somewhere above it in the widget tree, it has a TableRow as an ancestor.
In this app, you’re going to use TableCell because it makes alignment super-easy.
This widget knows how to control the children’s alignment in the context of the table.
To complete this example, look at the code for the cells themselves. This table has
four columns and seven rows. It would be cumbersome to write 28 widgets, so you’re
going generate each row. Later in this chapter, you explore what Flutter calls the
builder pattern, which is important and used commonly in Flutter apps.
This List.generate constructor function is going to execute at build time. If the con-
cept seems confusing, it’s fine to think of the List.generate function as a loop. It’s
going to run seven times. (The index at each loop iteration is actually be 0-6, though.)
At each iteration, you have an opportunity to do some logic. You know that in each
iteration, you have access to an index, which is different in each iteration, so you can
fetch the data for this widget without knowing what that data is.
List.generate is a Dart feature that’s not specific to Flutter, but it’s a great tool to
use when you need to build several widgets for a row, column, table, or list, each with
different data. Without using List.generate, you’d have to write more verbose code,
which would look something like this:
Ack! Even with all the content of each TableCell stripped out, you can see how cum-
bersome this code is, especially because each group of rows and cells is the same as every
other one. Using a function to build the rows programmatically is nice and common in
Flutter.
WARNING The caveat is that this example works only because the array of the
data is specifically ordered. If you can’t guarantee the order of your list, and if
order matters, this solution may not be the best one.
The important point is that all this code is doing is creating a list of widgets. It’s not the
most profound discovery, but it’s an example of the advantage of writing purely Dart
code without a markup language. Flutter takes advantage of this feature quite a bit.
The remaining code to implement creates the table rows themselves, which display
basic widgets by using TableCell, Text, Icon, and Padding. For the sake of familiariz-
ing yourself with Flutter code, here’s a snippet of the rows:
style: textStyle,
animation: textColorTween.animate(controller),
),
), This TableCell displays the icon that
), corresponds to current weather conditions.
TableCell(
child: ColorTransitionIcon(
icon: weatherIcon,
animation: textColorTween.animate(controller),
size: 16.0,
), This TableCell displays the daily high temperature.
),
TableCell(
child: ColorTransitionText(
text: _temperature(day.max).toString(),
style: textStyle,
animation: textColorTween.animate(controller),
),
), This TableCell displays the daily low temperature.
TableCell(
child: ColorTransitionText(
text: _temperature(day.min).toString(),
style: textStyle,
animation: textColorTween.animate(controller),
),
),
],
);
}),
// ...
This code is standard Flutter UI code. It’s adding four table cells to each row with stan-
dard table cells and other widgets. Outside the List.generate portion, there are no
special tricks here.
Finally, look at the code that adds this Table widget to the tree. It’s located in the
ForecastPageState.build method.
// weather_app/lib/page/forecast_page.dart
This reverses the direction
This column return Scaffold(
of the column. The first
houses all the appBar: // ...
child widget is at the
content of the body: new Stack(
bottom of the Column
ForecastPage. children: <Widget>[ render box. In this case,
// ... sun and clouds positioned widgets you want the content of
Column( (1) the column to be aligned at
This variable represents verticalDirection: VerticalDirection.up, the bottom of the screen
the Table widget. children: <Widget>[ and laid out accordingly.
forecastContent, This nice little feature of
mainContent, Flutter’s Column widget
// Flexible(child: timePickerRow), makes it much easier to
], achieve this alignment than
More widgets ), by writing your own code.
in the weather app. ],
),
);
Table generally isn’t much different from any other widgets, but the lessons in that
section are valuable. Soon, you’ll learn about the builder pattern, which is similar to
the List.generate method.
This diagram represents the basic idea of tabs. When you click an element on the tab
bar, the corresponding tab content changes. In the Flutter app, I use a tab bar to build
the row of times that can be clicked to update the temperature for that time of day.
The TabBar widget has two important pieces, one of which is the children—the
widgets that display the time of day that the user wants to select. The other important
part is TabController, which handles the functionality.
4.4.6 TabController
In Flutter, many widgets that involve interaction have corresponding controllers to man-
age events. ATextEditingController is used with widgets that allow users to type input,
for example. In this case, you’re using a TabController. The controller is responsible for
notifying Flutter app when a new tab is selected so that your app can update the tab to
display the desired content. The controller is created higher in the tree than the TabBar
and passed into the TabBar widget. This architecture is required because the parent of
the TabBar is also the parent of the tab widgets. For a concrete example, the TabBar
code in the weather app is in the weather_app/lib/widget/time_picker_row.dart file.
In that file, you’ll find the custom widget called TimePickerRow, a stateful widget
that displays the tabs and tells its parent when a tab-change event happens, by using
TabController.
Those are the important properties passed into the widget itself, but all the function-
ality lives in the State object.
// ...
}
setState is also important here. In the weather app, whenever you tap a different
time of day in the tab bar, the UI rerenders with the weather conditions for that time
of day. This action is possible because setState tells Flutter to rerender and to display
the newly selected tab when it does. The TabController.index getter refers the cur-
rently active tab.
The last note I’d like to make about the TabBar controller is that you don’t have to
change it directly. This object is used to get information about the tabs and to update
which tabs are active. You need only to interact with it, not extend it into a custom class.
At this point, you’ve seen all the moving parts of the TabBar. It’s a lot, but the import-
ant takeaways are this:
Using tabs requires a TabController and children widgets. The children are
the widgets that will be displayed and are tappable.
The functionality required to switch tabs when a widget in the TabBar is tapped
is done via a callback. This callback should use the properties exposed by the
TabController to tell Flutter when to render a new tab.
This mental paradigm is common in Flutter.
4
The documentation for ListView is at https://api.flutter.dev/flutter/widgets/ListView-class.html.
and it’s created with code similar to a row or column. This option is the most perfor-
mant one, but it may not be ideal if you have tens or hundreds of items to put in the
list or an unknown number of items.
What I want to focus on here, though, is the builder pattern in Flutter. The
builder pattern is used all over Flutter; it essentially tells Flutter to create widgets as
needed. In the default ListView constructor, Flutter builds all the children at the
same time and then renders. The ListView.builder constructor takes a callback in
the itemBuilder property, and that callback returns a widget. In this case, the callback
returns a ListTile widget. This builder makes Flutter smarter about rendering items
if you have a huge (or infinite) number of list items to display in your list. Flutter ren-
ders only the items that are visible onscreen.
Imagine a social media app like Twitter, which is an infinite list of tweets. It
wouldn’t be possible to render all the tweets in that list every time some state changes
because the number is infinite. Instead, the app renders as needed as the user scrolls
the tweets into view. This practice is common in UI, and Flutter provides ListView as
a built-in solution to this problem.
Here’s an example in the weather app. ListView is used on the SettingsPage.
ListView probably seems to be more complicated than many of the other widgets dis-
cussed in this chapter. The important piece of this example is builder. The ListView
builder is a simple way to create a scrolling list with potentially infinite items. That’s
often what the builder pattern is used for in Flutter: to create widgets that display
unknown data.
Summary
Flutter includes a ton of convenience structural widgets, such as MaterialApp,
Scaffold, and AppBar. These widgets give you an incredible amount for free:
navigation, menu drawers, theming, and more.
Use the SystemChrome class to manipulate features of the device itself, such as
forcing the app to be in landscape or portrait mode.
Use MediaQuery to get information about the screen size. This widget is useful if
you want to size widgets in a way that ensures that they scale by screen size.
Use Theme to set style properties that affect nearly every widget in your app.
Use the Stack widget to overlap widgets anywhere on the screen.
Use the Table widget to lay out widgets in a table.
ListView and its builder constructor give you a fast, performant way to create
lists with infinite items.
87
E K
emulators 10 keyboardShouldPersistTaps prop 5
Enable Hot Reloading option 11
Enable Live Reload option 11 L
entry point 60
laying out apps 3
F leading action, AppBar widget property 66
List.generate() constructor 77–80
FirstViewModel class 53 listeners 82
flexed columns 76 ListView widget 58, 84–86
builder pattern 85
Flutter
described 84
and constraints of widgets’ sizes 66
flexibility 84
and diagram of tab-related widgets in 80 frequent use of 84
as a complete SDK 57 Twitter 85
built-in widgets 57
Dart code 77
M
importing iOS icons 60
logical pixel 69 main function 60
main.dart 60 MaterialApp widget 86
of method 70 helpful functionality and 62
specific configuration items 59 Material widget library 62
styling in 67 WidgetsApp and 62
SystemChrome and 60–61 MediaQuery class 58
TextEditingController 81 MediaQuery widget 86
weather app example 57–86 of method 69
background of 72 MediaQuery.of(context).size, static method 69
file structure 59 mobile platforms 41–42
main function 60 MVVM (model-view-view model) design pattern
table code from 77 creating cross-platform apps 53–54
flutter_in_action repository 59 creating solutions 40–52
cross-platform code 37–40
ForecastPage widget, weather app example 65
UI design patterns 32–33
Future, and asynchronous programming in
overview of 33–37
Dart 61
N
G
Name property 35
getVisibleTodos function 27 .NET Standard specification
class libraries 38–40
H New Solution dialog box 44
Number property 36
hot reloading 11
O
I
onPress method 24
Input component 12
inputChange function 8, 13 P
inputValue property 5, 13
iOS operating system, building and running apps PCLs (portable class libraries) 38
on 52 placeholderTextColor 8
T UI (user interface)
designing, design patterns 32–33
TabBar component 25 UI layer 33, 36
TabBar widget 58 UI logic layer 37
in practice 83–84 underlayColor property 16
V convenience widgets 58
iOS-style 60
versioned packages 48 layout widgets 58
Visual Studio ListView 84–86
for Mac, creating solutions with 42–45 making a widget positioned 73
for Windows, creating solutions with 45–47 MaterialApp 62–63
MediaQuery 69–70
W PageContainer 63
PreferredSize 66–67
widgets Scaffold 63–65
and of method 69 Stack widget 72–74
AppBar 65 structural 58
building-block widgets 58 TabBar 80–84
built-in, Flutter and 57 Table widget 74
configuring structural 61–67 Theme 67–69
constraints and final size 66 WidgetsApp 62