Week 7 [Oct 1]
Todo
Admin info to read:
Overview: Update UG and DG in the repo, update project plan in repo, attempt to do global-impact changes to the code base.
v1.1 Summary of Milestone
Milestone | Minimum acceptable performance to consider as 'reached' |
---|---|
Team org/repo set up | as stated |
Project plan formalized | as stated |
Have done some enhancement to the code | at least attempted to enhance the code of a component, even if you did not fully succeed |
All project documents updated as required | updated docs are merged to the master branch |
Reminder: Reaching individual and team milestones are considered for
A. Process:
Evaluates: How well you did in project management related aspects of the project, as an individual and as a team
Based on: Supervisor observations of project milestones and GitHub data.
Milestones need to be reached the midnight before of the tutorial for it to be counted as achieved. To get a good grade this aspect, achieve recommended weekly progress in at least 6/10 weeks.
Other criteria:
- Good use of GitHub milestones
- Good use of GitHub release mechanism
- Good version control, based on the repo
- Reasonable attempt to use the forking workflow
- Good task definition, assignment and tracking, based on the issue tracker
- Good use of buffers (opposite: everything at the last minute)
- Project done iteratively and incrementally (opposite: doing most of the work in one big burst)
B. Team-based tasks:
Evaluates: How much you contributed to common team-based tasks
Based on: peer evaluations and tutor observations
Relevant: [
Here is a non-exhaustive list of team-tasks:
- Necessary general code enhancements e.g.,
- Work related to renaming the product
- Work related to changing the product icon
- Morphing the product into a different product
- Setting up the GitHub, Travis, AppVeyor, etc.,
- Maintaining the issue tracker
- Release management
- Updating user/developer docs that are not specific to a feature e.g. documenting the target user profile
- Incorporating more useful tools/libraries/frameworks into the product or the project workflow (e.g. automate more aspects of the project workflow using a GitHub plugin)
v1.1 Project Management
-
Fix any errors in org/repo set up (e.g. wrong repo name).
-
When all changes intended for v1.1 is merged to the master branch, use
git tag
feature to tag the current version asv1.1
-
Start tracking your project schedule using GitHub issue tracker and milestones. More instructions coming soon.
v1.1 Documentation
-
Update User Guide, Developer Guide, README, and About Us pages as described earlier.
Submission: merge your changes to the master branch of your repo.
v1.1 Product
- Each member should try to add some enhancements that are in line with the vision for v2.0. Preferably, these should be
global-impact enhancements, touching as many other components as possible. Refer to the AddressBook-Level4 Developer Guide has some guidance on how to implement a new feature end-to-end.
If your team is facing difficulties due to differences in skill/motivation /availability among team members,
-
First, do not expect everyone to have the same skill/motivation level as you. It is fine if someone wants to do less and have low expectations from the module. That doesn't mean that person is a bad person. Everyone is entitled to have their own priorities.
-
Second, don't give up. It is unfortunate that your team ended up in this situation, but you can turn it into a good learning opportunity. You don't get an opportunity to save a sinking team every day 😃
-
Third, if you care about your grade and willing to work for it, you need to take initiative to turn the situation around or else the whole team is going to suffer. Don't hesitate to take charge if the situation calls for it. By doing so, you'll be doing a favor for your team. Be professional, kind, and courteous to the team members, but also be firm and assertive. It is your grade that is at stake. Don't worry about making a bad situation worse. You won't know until you try.
-
Finally, don't feel angry or 'wronged'. Teamwork problems are not uncommon in this module and we know how to grade so that you will not be penalized for others' low contribution. We can use Git to find exactly what others did. It's not your responsibility to get others to contribute.
Given below are some suggestions you can adopt if the project work is not going smooth due to team issues. Note that the below measures can result in some team members doing more work than others and earning better project grades than others. It is still better than sinking the whole team together.
-
Redistribute the work: Stronger programmers in the team should take over the critical parts of the code.
-
Enforce stricter integration workflow: Appoint an integrator (typically, the strongest programmer). His/her job is to maintain the integrated version of the code. He/she should not accept any code that breaks the existing product or is not up to the acceptable quality standard. It is up to others to submit acceptable code to the integrator. Note that if the integrator rejected your code unreasonably, you can still earn marks for that code. You are allowed to submit such 'rejected' code for grading. They can earn marks based on the quality of the code.
If you have very unreliable or totally disengaged team members :
- Re-allocate to others any mission-critical work allocated to that person so that such team members cannot bring down the entire team.
- However, do not leave out such team members from project communications. Always keep them in the loop so that they can contribute any time they wish to.
- Furthermore, evaluate them sincerely and fairly during peer evaluations so that they do get the grade their work deserves, no more, no less.
- Be courteous to such team members too. Some folks have genuine problems that prevent them from contributing more although they may not be able tell you the reasons. Just do your best for the project and assume everyone else is doing their best too, although their best may be lower than yours.
Why I’m not allowed to use my favorite tool/framework/language etc.?
We have chosen a basic set of tools after considering ease of learning, availability, typical-ness, popularity, migration path to other tools, etc. There are many reasons for limiting your choices:
Pedagogical reasons:
- Sometimes 'good enough', not necessarily the best, tools are a better fit for beginners: Most bleeding edge, most specialized, or most sophisticated tools are not suitable for a beginner course. After mastering our toolset, you will find it easy to upgrade to such high-end tools by yourself. We do expect you to eventually (after this module) migrate to better tools and, having learned more than one tool, to attain a more general understanding about a family of tools.
- We want you to learn to thrive under given conditions: As a professional Software Engineer, you must learn to be productive in any given tool environment, rather than insist on using your preferred tools. It is usually
in small companies doing less important work that you get to chose your own toolset. Bigger companies working on mature products often impose some choices on developers, such as the project management tool, code repository, IDE,
language etc. For example, Google used SVN as their revision control software until very recently, long after SVN fell out of popularity among developers. Sometimes this is due to cost reasons (tool licensing cost), and sometimes
due to legacy reasons (because the tool is already entrenched in their code base).
While programming in school is often a solo sport, programming in the industry is a team sport. As we are training you to become professional software engineers, it is important to get over the psychological hurdle of needing to satisfy individual preferences and get used to making the best of a given environment.
Practical reasons:
- Some of the LOs are tightly coupled to tools. Allowing more tools means tutors need to learn more tools, which increases their workload.
- We provide learning resources for tools. e.g. 'Git guides'. Allowing more tools means we need to produce more resources.
- When all students use the same tool, the collective expertise of the tool is more, increasing the opportunities for you to learn from each others.
Meanwhile, feel free to share with peers your experience of using other tools.
Outcomes
Design
W7.1
Can interpret basic sequence diagrams
W7.1a
Can explain/identify sequence diagrams
Tools → UML → Sequence Diagrams →
Introduction
A UML sequence diagram captures the interactions between multiple objects for a given scenario.
Consider the code below.
class Machine {
Unit producePrototype() {
Unit prototype = new Unit();
for (int i = 0; i < 5; i++) {
prototype.stressTest();
}
return prototype;
}
}
class Unit {
public void stressTest() {
}
}
Here is the sequence diagram to model the interactions for the method call producePrototype()
on a Machine
object.
W7.1b
Can interpret sequence diagrams with basic notation
Tools → UML → Sequence Diagrams →
Basic
Notation:
This sequence diagram shows some interactions between a human user and the Text UI of a
The player runs the newgame
action on the TextUi
object which results in the TextUi
showing the minefield to the player. Then, the player runs the clear x y
command; in response,
the TextUi
object shows the updated minefield.
The :TextUi
in the above example denotes an unnamed instance of the class TextUi. If there were two instances of TextUi
in the diagram, they can be distinguished by naming them e.g. TextUi1:TextUi
and TextUi2:TextUi
.
Arrows representing method calls should be solid arrows while those representing method returns should be dashed arrows.
Note that unlike in object diagrams, the class/object name is not underlined in sequence diagrams.
[Common notation error] Activation bar too long: The activation bar of a method cannot start before the method call arrives and a method cannot remain
active after the method had returned. In the two sequence diagrams below, the one on the left commits this error because the activation bar starts before the method Foo#xyz()
is called and remains active after the method returns.
[Common notation error] Broken activation bar: The activation bar should remain unbroken from the point the method is called until the method returns.
In the two sequence diagrams below, the one on the left commits this error because the activation bar for the method Foo#abc()
is not contiguous, but appears as two pieces instead.
W7.1c
Can interpret sequence diagrams with loops
Tools → UML → Sequence Diagrams →
Loops
Notation:
The Player
calls the mark x,y
command or clear x y
command repeatedly until the game is won or lost.
W7.1d
Can interpret sequence diagrams with object creation
Tools → UML → Sequence Diagrams →
Object Creation
Notation:
- The arrow that represents the constructor arrives at the side of the box representing the instance.
- The activation bar represents the period the constructor is active.
The Logic
object creates a Minefield
object.
W7.1e
Can interpret sequence diagrams with minimal notation
Tools → UML → Sequence Diagrams →
Minimal Notation
To reduce clutter, activation bars and return arrows may be omitted if they do not result in ambiguities or loss of information. Informal operation descriptions such as those given in the example below can be used, if more precise details are not required for the task at hand.
A minimal sequence diagram
W7.1f
Can draw basic sequence diagrams
Design → Modelling → Modelling Behaviors
Sequence Diagrams - Basic
Explain in your own words the interactions illustrated by this Sequence Diagram:
Consider the code below:
class Person{
Tag tag;
String name;
Person(String personName, String tagName){
name = personName;
tag = new Tag(tagName);
}
}
class Tag{
Tag(String value){
//...
}
}
class PersonList{
void addPerson(Person p){
//...
}
}
Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:
PersonList personList = new PersonList();
while (hasRoom){
Person p = new Person("Adam", "friend");
personList.addPerson(p);
}
Find notation mistakes in the sequence diagram below:
Evidence:
Consider the code below:
class Person{
Tag tag;
String name;
Person(String personName, String tagName){
name = personName;
tag = new Tag(tagName);
}
}
class Tag{
Tag(String value){
//...
}
}
class PersonList{
void addPerson(Person p){
//...
}
}
Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:
PersonList personList = new PersonList();
while (hasRoom){
Person p = new Person("Adam", "friend");
personList.addPerson(p);
}
Submission: Show during tutorial.
W7.2
Can explain APIs
W7.2a
Can explain APIs
Implementation → Reuse → APIs →
What
An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.
A class has an API (e.g., API of the Java String
class, API of the Python str
class)
which is a collection of public methods that you can invoke to make use of the class.
The GitHub API is a collection of Web request formats GitHub server accepts and the corresponding responses. We can write a program that interacts with GitHub through that API.
When developing large systems, if you define the API of each components early, the development team can develop the components in parallel because the future behavior of the other components are now more predictable.
Choose the correct statements
- a. A software component can have an API.
- b. Any method of a class is part of its API.
- c. Private methods of a class are not part of its API.
- d. The API forms the contract between the component developer and the component user.
- e. Sequence diagrams can be used to show how components interact with each other via APIs.
(a) (c) (d) (e)
Explanation: (b) is incorrect because private methods cannot be a part of the API
Defining component APIs early is useful for developing components in parallel.
True
Explanation: Yes, once we know the precise behavior expected of each component, we can start developing them in parallel.
Evidence:
Know the API of the AddressBook component you are in charge of and the APIs of the other components your component depends on.
W7.3
Can use intermediate-level sequence diagrams
W7.3a
Can interpret sequence diagrams with object deletion
Tools → UML → Sequence Diagrams →
Object Deletion
UML uses an X
at the end of the lifeline of an object to show it's deletion.
💡 Although object deletion is not that important in languages such as Java that support automatic memory management, you can still show object deletion in UML diagrams to indicate the point at which the object ceases to be used.
Notation:
Note how the diagrams shows the deletion of the Minefield
object
W7.3b
Can interpret sequence diagrams with self invocation
Tools → UML → Sequence Diagrams →
Self Invocation
UML can show a method of an object calling another of its own methods.
Notation:
The markCellAt(...)
method of a Logic
object is calling its own updateState(...)
method.
In this variation, the Book#write()
method is calling the Chapter#getText()
method which in turn does a call back by calling the getAuthor()
method
of the calling object.
W7.3c
Can interpret sequence diagrams with alternative paths
Tools → UML → Sequence Diagrams →
Alternative Paths
UML uses alt
frames to indicate alternative paths.
Notation:
Minefield
calls the Cell#setMine
if the cell is supposed to be a mined cell, and calls the Cell:setMineCount(...)
method otherwise.
W7.3d
Can interpret sequence diagrams with optional paths
Tools → UML → Sequence Diagrams →
Optional Paths
UML uses opt
frames to indicate optional paths.
Notation:
Logic#markCellAt(...)
calls Timer#start()
only if it is the first move of the player.
W7.3e
Can interpret sequence diagrams with reference frames
Tools → UML → Sequence Diagrams →
Reference Frames
UML uses ref frame to allow a segment of the interaction to be omitted and shown as a separate sequence diagram. Reference frames help us to break complicated sequence diagrams into multiple parts or simply to omit details we are not interested in showing.
Notation:
The details of the get minefield appearance
interactions have been omitted from the diagram.
Those details are shown in a separate sequence diagram given below.
W7.3f
Can draw intermediate-level sequence diagrams
Design → Modelling → Modelling Behaviors
Sequence Diagrams - Intermediate
What’s going on here?
- a.
Logic
object is executing a parallel thread. - b.
Logic
object is executing a loop. - c.
Logic
object is creating anotherLogic
instance. - d. One of
Logic
object’s methods is calling another of its methods. - e.
Minefield
object is calling a method ofLogic
.
(d)
Explain the interactions depicted in this sequence diagram.
First, the createParser()
method of an existing ParserFactory
object is called. Then, ...
Draw a sequence diagram to represent this code snippet.
if (isFirstPage) {
new Quote().print();
}
The Quote
class:
class Quote{
String q;
Quote(){
q = generate();
}
String generate(){
// ...
}
void print(){
System.out.println(q);
}
}
- Show
new Quote().print();
as two method calls. - As the created Quote object is not assigned to a variable, it can be considered as 'deleted' soon after its
print()
method is called.
Evidence:
Explain the interactions depicted in this sequence diagram.
First, the createParser()
method of an existing ParserFactory
object is called. Then, ...
- Explain the sequence diagrams given [AddressBook Level4: Developer Guide]
- Add more sequence diagrams to project documentation (to be done in future weeks)
W7.3g
Can interpret sequence diagrams with parallel paths
Tools → UML → Sequence Diagrams →
Parallel Paths
UML uses par
frames to indicate parallel paths.
Notation:
Logic
is calling methods CloudServer#poll()
and LocalServer#poll()
in parallel.
💡 If you show parallel paths in a sequence diagram, the corresponding Java implementation is likely to be multi-threaded because a normal Java program cannot do multiple things at the same time.
Implementation
W7.4
Can use logging
W7.4a
Can explain logging
Implementation → Error Handling → Logging →
What
Logging is the deliberate recording of certain information during a program execution for future reference. Logs are typically written to a log file but it is also possible to log information in other ways e.g. into a database or a remote server.
Logging can be useful for troubleshooting problems. A good logging system records some system information regularly. When bad things happen to a system e.g. an unanticipated failure, their associated log files may provide indications of what went wrong and action can then be taken to prevent it from happening again.
💡 A log file is like the
Why is logging like having the 'black box' in an airplane?
(a)
W7.4b
Can use logging
Implementation → Error Handling → Logging →
How
Most programming environments come with logging systems that allow sophisticated forms of logging. They have features such as the ability to enable and disable logging easily or to change the logging
This sample Java code uses Java’s default logging mechanism.
First, import the relevant Java package:
import java.util.logging.*;
Next, create a Logger
:
private static Logger logger = Logger.getLogger("Foo");
Now, you can use the Logger
object to log information. Note the use of
WARNING
so that log messages specified as INFO
level (which is a lower level than WARNING
)
will not be written to the log file at all.
// log a message at INFO level
logger.log(Level.INFO, "going to start processing");
//...
processInput();
if(error){
//log a message at WARNING level
logger.log(Level.WARNING, "processing error", ex);
}
//...
logger.log(Level.INFO, "end of processing");
Tutorials:
- Java Logging API - Tutorial -- A tutorial by Lars Vogella
- Java Logging Tutorial -- An alternative tutorial by Jakob Jenkov
- A video tutorial by SimplyCoded:
Best Practices:
- 10 Tips for Proper Application Logging -- by Tomasz Nurkiewicz
- What each logging level means -- conventions recommended by Apache Project
Evidence:
Use of logging in the code you have written in the module project or elsewhere.
W7.5
Can use assertions
W7.5a
Can explain assertions
Implementation → Error Handling → Assertions →
What
Assertions are used to define assumptions about the program state so that the runtime can verify them. An assertion failure indicates a possible bug in the code because the code has resulted in a program state that violates an assumption about how the code should behave.
An assertion can be used to express something like when the execution comes to this point, the variable v
cannot be null.
If the runtime detects an assertion failure, it typically take some drastic action such as terminating the execution with an error message. This is because an assertion failure indicates a possible bug and the sooner the execution stops, the safer it is.
In the Java code below, suppose we set an assertion that timeout
returned by Config.getTimeout()
is greater than 0
. Now, if the Config.getTimeout()
returned -1
in a specific execution of this line, the runtime can detect it as a assertion failure -- i.e. an assumption about the expected behavior of the code turned out to be wrong which could potentially
be the result of a bug -- and take some drastic action such as terminating the execution.
int timeout = Config.getTimeout();
W7.5b
Can use assertions
Implementation → Error Handling → Assertions →
How
Use the assert
keyword to define assertions.
This assertion will fail with the message x should be 0
if x
is not 0 at this point.
x = getX();
assert x == 0 : "x should be 0";
...
Assertions can be disabled without modifying the code.
java -enableassertions HelloWorld
(or java -ea HelloWorld
) will run HelloWorld
with assertions enabled while java -disableassertions HelloWorld
will run it without verifying assertions.
Java disables assertions by default. This could create a situation where you think all assertions are being verified as true
while in fact they are not being verified at all. Therefore, remember
to enable assertions when you run the program if you want them to be in effect.
💡 Enable assertions in Intellij (how?) and get an assertion to fail temporarily (e.g. insert an assert false
into the code temporarily) to confirm assertions are being verified.
Java assert
vs JUnit assertions: They are similar in purpose but JUnit assertions are more powerful and customized for testing. In addition, JUnit assertions
are not disabled by default. We recommend you use JUnit assertions in test code and Java assert
in functional code.
Tutorials:
- Java Assertions -- a simple tutorial from javatpoint.com
- Programming with Assertions (first half) -- a more detailed tutorial from Oracle
Best practices:
- Programming with Assertions (second half) -- from Oracle (also listed above as a tutorial) contains some best practices towards the end of the article.
Evidence:
Explain assertions in AddressBook-Level4 code.
W7.5c
Can use assertions optimally
Implementation → Error Handling → Assertions →
When
It is recommended that assertions be used liberally in the code. Their impact on performance is considered low and worth the additional safety they provide.
Do not use assertions to do work because assertions can be disabled. If not, your program will stop working when assertions are not enabled.
The code below will not invoke the writeFile()
method when assertions are disabled. If that method is performing some work that is necessary for your program, your program will not work correctly when assertions are disabled.
...
assert writeFile() : "File writing is supposed to return true";
Assertions are suitable for verifying assumptions about Internal Invariants, Control-Flow Invariants, Preconditions, Postconditions, and Class Invariants. Refer to [Programming with Assertions (second half)] to learn more.
Exceptions and assertions are two complementary ways of handling errors in software but they serve different purposes. Therefore, both assertions and exceptions should be used in code.
- The raising of an exception indicates an unusual condition created by the user (e.g. user inputs an unacceptable input) or the environment (e.g., a file needed for the program is missing).
- An assertion failure indicates the programmer made a mistake in the code (e.g., a null value is returned from a method that is not supposed to return null under any circumstances).
A Calculator program crashes with an ‘assertion failure’ message when you try to find the square root of a negative number.
(c)
Explanation: An assertion failure indicates a bug in the code. (b) is not acceptable because of the word "terminated". The application should not fail at all for this input. But it could have used an exception to handle the situation internally.
Which statements are correct?
- a. Use assertions to indicate the programmer messed up; Use exceptions to indicate the user or the environment messed up.
- b. Use exceptions to indicate the programmer messed up; Use assertions to indicate the user or the environment messed up.
(a)
Evidence:
Give an example from the AddressBook-Level4 code where an exception is used and explain why an assertion is not suitable for that situation. Similarly, explain why an exception is not suitable for a place where AddressBook Level4 uses an assertion.
W7.6
Can do exception handling in code
W7.6a
Can explain error handling
Implementation → Error Handling → Introduction →
What
Well-written applications include error-handling code that allows them to recover gracefully from unexpected errors. When an error occurs, the application may need to request user intervention, or it may be able to recover on its own. In extreme cases, the application may log the user off or shut down the system. --Microsoft
W7.6b
Can explain exceptions
Implementation → Error Handling → Exceptions →
What
Exceptions are used to deal with 'unusual' but not entirely unexpected situations that the program might encounter at run time.
Exception:
The term exception is shorthand for the phrase "exceptional event." An exception is an event, which occurs during the execution of a program, that disrupts the normal flow of the program's instructions. –- Java Tutorial (Oracle Inc.)
Examples:
- A network connection encounters a timeout due to a slow server.
- The code tries to read a file from the hard disk but the file is corrupted and cannot be read.
W7.6c
Can explain how exception handling is done typically
Implementation → Error Handling → Exceptions →
How
Most languages allow code that encountered an "exceptional" situation to encapsulate details of the situation in an Exception object and throw/raise that object so that another piece of code can catch it and deal with it. This is especially useful when the code that encountered the unusual situation does not know how to deal with it.
The extract below from the -- Java Tutorial (with slight adaptations) explains how exceptions are typically handled.
When an error occurs at some point in the execution, the code being executed creates an exception object and hands it off to the runtime system. The exception object contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handing it to the runtime system is called throwing an exception.
After a method throws an exception, the runtime system attempts to find something to handle it in the
The exception handler chosen is said to catch the exception. If the runtime system exhaustively searches all the methods on the call stack without finding an appropriate exception handler, the program terminates.
Advantages of exception handling in this way:
- The ability to propagate error information through the call stack.
- The separation of code that deals with 'unusual' situations from the code that does the 'usual' work.
Which are benefits of exceptions?
- a. Exceptions allow us to separate normal code from error handling code.
- b. Exceptions can prevent problems that happen in the environment.
- c. Exceptions allow us to handle in one location an error raised in another location.
(a) (c)
Explanation: Exceptions cannot prevent problems in the environment. They can only be used to handle and recover from such problems.
Evidence:
Acceptable: Some code you wrote that involves exception handling.
Suggested: Do the exercise in addressbook-level2: LO-Exceptions.
Submission:
- Options 1 (discouraged): Show the relevant code during the tutorial.
- Options 2 (preferred): Create a PR against Addressbook-Level2 by following the instructions below.
If you choose option 2, we recommend that you complete this week's Project Management LOs first; there are many ways to create PRs but we expect you to create PRs in a specific way, as specified in the LOs.
W7.6d
Can avoid using exceptions to control normal workflow
Implementation → Error Handling → Exceptions →
When
In general, use exceptions only for 'unusual' conditions. Use normal return
statements to pass control to the caller for conditions that are 'normal'.
W7.7
Can use Java8 streams
W7.7a
Can use Java8 streams
Tools → Java →
Streams: Basic
Java 8 introduced a number of new features (e.g. Lambdas, Streams) that are not trivial to learn but also extremely useful to know.
Here is an overview of new Java 8 features . (written by Benjamin Winterberg)
Tutorials:
- Java 8 Tutorial -- from tutorialspoint.com. 💡 Also provides a way to try out code online
- Tutorials from Oracle: [Lambdas][Streams]
A video tutorial by well-known Java coach Venkat Subramaniam
Evidence:
- Your code (can be toy examples) that use some Java 8 features.
- Explain some parts of [AddressBook - Level 4] code that use Java 8 features. e.g.
AddressBook#syncMasterTagListWith(Person)
W7.8
Can use JavaFX to build a simple GUI
W7.8a
Can use JavaFX to build a simple GUI
Tools → Java →
JavaFX: Basic
Adapted (with permissions) from Marco Jakob's JavaFX 8 tutorial.
JavaFx 9 Tutorial - Part 1: Scene Builder
Introduction
This tutorial will teach you how to create a new JavaFX application in IntelliJ, and to use the SceneBuilder to create a layouts for your application.
Prerequisites
- Latest Java JDK 9 (includes JavaFX 9)
- IntelliJ (2018.2 or later)
- SceneBuilder 8 (provided by Gluon as Oracle no longer ships the tool in binary form)
Do remember the installation path to SceneBuilder 8 as we will need it to configure IntelliJ in a later step.
IntelliJ Configurations
If this is the first time using IntelliJ, you need to tell IntelliJ where to find JDK 9 and SceneBuilder.
Configuring JDK 9
- On the Welcome screen, press
Configure
→Project Default
→Project Structure
.
- If you already have a project open, go to the Welcome screen by going to
File
→Close Project
.
- Under
Project SDK:
, pressNew...
→JDK
. - Select the directory that you install JDK on, and press
OK
. - Under
Project language level:
, select9 - Modules, private methods in interfaces etc.
.
- Press
OK
again.
Configuring Scene Builder
- On the Welcome screen, press
Configure
→Settings
. - On the left hand side, select
Languages & Frameworks
→JavaFX
- Under
Path to SceneBuilder:
, select the path to where SceneBuilder is installed (e.g.C:\Users\Admin\AppData\Local\SceneBuilder\SceneBuilder.exe
on Windows)
The JavaDocs will come in handy when writing your own JavaFX applications:
Additionally, Oracle also has a tutorial on JavaFX if you are interested.
Create a new JavaFX Project
- On the Welcome screen, press
Create New Project
.
- If you already have a project, you can create a new project by going
File
→New
→Project...
.
- On the left side, select
JavaFX
. Make sure that the Project SDK is set to9
andJavaFX Application
is selected.
- Press
Next
. - Give a name for the application (e.g.
AddressApp
), and specify a suitable location. - Press
Finish
. If prompted to create a new directory, just pressOK
.
Remove the sample
package and its content. We will manually create our own package and resources in our tutorial.
We will also have to set up the IDE further, so that warnings and errors show up correctly when working with Java 9:
- Go to the menu
File
→Project Structure
. - Under
Project language level:
, ensure that9 - Modules, private methods in interfaces etc.
is selected.
Create the Packages
In We will create a package for each of the component. Ensure that your Project pane is open (Alt+1). Right click on the src
folder, and select New
→ Package
:
seedu.address
- contains the controller classes (i.e. the part that deals with the business logic)seedu.address.model
- contains the model classes (i.e. the part that deals with data)seedu.address.view
- contains the views (i.e. the part that deals with presenting the data to the user)
In subsequent tutorials, our view
package will also contain some controllers that are directly related to a single view. We will call them view-controllers
.
Create the FXML Layout File
There are two ways to create the UI:
- Use FXML, which is an XML format.
- Programmatically create the interface in Java.
We will use FXML for most parts, so that we can separate the view and controller from each other. Furthermore, we are able to use the Scene Builder tool to edit our FXML file. That means we will not have to directly work with XML.
Right click on the view
package, and press New
→ FXML file
. For the file name, type PersonOverview.fxml
.
Design with Scene Builder
Right-click on PersonOverview.fxml
and choose Open with Scene Builder
. Now you should see the Scene Builder with just an AnchorPane (visible under Hierarchy on the left).
If IntelliJ prompts for a location of the SceneBuilder executable, make sure to point to where you install SceneBuilder.
- Select the
Anchor Pane
in your Hierarchy, and adjust the size under Layout (right side). (Pref Width: 600, Pref Height: 300)
- Add a
Split Pane (horizontal)
(underContainers
) by dragging it from the Library into the main area. Right-click theSplit Pane
in the Hierarchy view and selectFit to Parent
.
- Drag a
TableView
(underControls
in Library view) into the left side of theSplitPane
. Select theTableView
(not a Column) and set the following layout constraints in the Inspector to theTableView
. Inside anAnchorPane
you can always set anchors to the four borders (see this page for more information on Layouts).
-
Go to the menu
Preview
→Show Preview in Window
to see whether the layout configuration is done correctly. Try resizing the window. TheTableView
should resize together with the window as it is anchored to the borders. -
Change the column text (under Properties) to "First Name" and "Last Name".
- Select the
TableView
and chooseconstrainted-resize
for the 'Column Resize Policy'. This ensures that the columns will always fill up the entire available space.
- Add a
Label
on the right side with the text "Person Details:". Adjust the layout using anchors (Top: 5, Left: 5. Right: Blank, Bottom: Blank).
You can use the Search Bar at the top of Library/Inspector to find the respective controls/properties.
- Add a
GridPane
on the right side. Select it, and adjust its layout using anchors (Top: 30, Left: 5, Right: 5, Bottom: Blank).
- Add the following labels to the cells, such that the grid is of this form:
First Name | Label |
---|---|
Last Name | Label |
Street | Label |
City | Label |
Postal Code | Label |
Birthday | Label |
To add a row to the GridPane, select an existing row number, right click the row number and choose "Add Row Below".
- Add a
ButtonBar
at the bottom. Add three buttons to the bar ("New...", "Edit...","Delete"). Adjust the anchors so that it stays at the bottom right (Top: Blank, Left: Blank, Right: 10, Bottom: 5).
- Now you should see something like the following. Use the
Preview
menu to test its resizing behaviour.
- Save the
.fxml
file.
Create the Main Application
The PersonOverview.fxml
that we just created only contains the content of our entire application. We need another FXML for our root layout, which will contain a menu bar and wraps PersonOverview.fxml
.
- Inside IntelliJ, right click on the
view
package, and pressNew
→FXML file
. For the file name, typeRootLayout.fxml
. - Right-click on
RootLayout.fxml
and chooseOpen with Scene Builder
. - Delete the
AnchorPane
. We will use another pane for our root layout.
- Add
BorderPane
by dragging it from the Library view into the main area.
- Resize the
BorderPane
(Pref Width: 600, Pref Height: 400)
- Add a
MenuBar
into theinsert TOP
slot. We will not implement menu functionality for now.
The JavaFX Main Class
Now, we need to create the main Java class that starts up our application with the RootLayout.fxml
and adds the PersonOverview.fxml
in the center.
Right-click on your seedu.address
package, and choose New
→ JavaFXApplication
. For the class name, type MainApp
.
The generated MainApp.java
class extends from Application
and contains two methods. This is the basic structure that we need to start a JavaFX Application. The most important part for us is the start(Stage primaryStage)
method. It is automatically called when the application is launch()
from within the main()
method.
As you see, the start(...)
method receives a Stage
as parameter. The following graphic illustrates the structure of every JavaFX application:
Image Source: http://www.oracle.com
It's like a theater play: The Stage
is the main container which is usually a Window
with a border and the typical minimize, maximize and close buttons. Inside the Stage
you add
a Scene
which can, of course, be switched out by another Scene
. Inside the Scene
the actual JavaFX nodes like AnchorPane
, TextBox
, etc. are added.
See this page for more info on working with the JavaFX Scene Graph.
Open MainApp.java
and replace the code with the following:
MainApp.java
package seedu.address;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainApp extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
@Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("AddressApp");
initRootLayout();
showPersonOverview();
}
/**
* Initializes the root layout.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/RootLayout.fxml"));
rootLayout = loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Shows the person overview inside the root layout.
*/
public void showPersonOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
AnchorPane personOverview = loader.load();
// Set person overview into the center of root layout.
rootLayout.setCenter(personOverview);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns the main stage.
*/
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
}
The various comments should give you some hints about what's going on.
Importing JavaFx 9
JavaFx 9 is a separate module from Java 9. We must ensure that the modules are imported correctly.
Notice that IntellJ will complain about the imported modules "not being in the module graph":
To fix this:
- Ensure that your Project pane is open (Alt+1). Right click on the
src
folder, and selectNew
→module-info.java
: - Add the following
requires
andexports
in order to import and export the modules correctly:
module AddressApp {
requires javafx.graphics;
requires javafx.fxml;
requires javafx.controls;
exports seedu.address;
}
Running the Application
If you run the application now (right click MainApp.java
and select Run MainApp.main()
), you should see something like this:
Possible Issues
If JavaFx fails to load PersonOverview.fxml
, you might get the following error message:
javafx.fxml.LoadException:
/.../AddressApp/out/production/AddressApp/seedu/address/view/PersonOverview.fxml:15
To solve this issue, open PersonOverview.fxml
normally in IntelliJ and ensure that there is no such attribute around:
fx:controller="seedu.address.view.PersonOverview"
JavaFx 9 tutorial - Part 2: Model and TableView
Introduction
In this tutorial, we will create the other parts of the application (mainly the model and the controller). We will also use ObservableList
and *Property
to bind our list of new model's Person
s,
and their individual details, to the view, with the controller as the "middleman".
Create the Model class
We need a model class in order to hold information about the people in our address book. Add a new class to the model
package (seedu.address.model
) called Person
. The Person
class will encapsulate the details of an individual person such as name, address and birthday. Add the following code to the class. The JavaFX specifics will be explained after the code snippet.
Person.java
package seedu.address.model;
import java.time.LocalDate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Model class for a Person.
*/
public class Person {
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty street;
private final IntegerProperty postalCode;
private final StringProperty city;
private final ObjectProperty< LocalDate > birthday;
/**
* Default constructor.
*/
public Person() {
this(null, null);
}
/**
* Constructor with some initial data.
*/
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
// Some initial dummy data, just for convenient testing.
this.street = new SimpleStringProperty("some street");
this.postalCode = new SimpleIntegerProperty(1234);
this.city = new SimpleStringProperty("some city");
this.birthday = new SimpleObjectProperty< LocalDate >(LocalDate.of(1999, 2, 21));
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String getStreet() {
return street.get();
}
public void setStreet(String street) {
this.street.set(street);
}
public StringProperty streetProperty() {
return street;
}
public int getPostalCode() {
return postalCode.get();
}
public void setPostalCode(int postalCode) {
this.postalCode.set(postalCode);
}
public IntegerProperty postalCodeProperty() {
return postalCode;
}
public String getCity() {
return city.get();
}
public void setCity(String city) {
this.city.set(city);
}
public StringProperty cityProperty() {
return city;
}
public LocalDate getBirthday() {
return birthday.get();
}
public void setBirthday(LocalDate birthday) {
this.birthday.set(birthday);
}
public ObjectProperty< LocalDate > birthdayProperty() {
return birthday;
}
}
Explanations
- With JavaFX, it is common to use
*Property
for all fields of a model class. AProperty
allows us, for example, to automatically be notified when thelastName
or any other variable is changed. This helps us keep the view in sync with the data. - To learn more about
*Property
, refer to Using JavaFX Properties and Binding LocalDate
, the type that we are using forbirthday
, is part of the new Date and Time API since JDK 8
A List of Persons
The main data that our application manages is simply a bunch of persons. Let's create a list of Person
objects inside the MainApp
class. All other controller classes will later get access to that central
list inside the MainApp
.
ObservableList
We are working with JavaFX view classes that need to be informed about any changes made to the list of persons. This is important, since otherwise the view would not be in sync with the data. For this purpose, JavaFX introduces some new Collection classes.
Among all these collections, we need the ObservableList
. To create a new ObservableList
, add the following code at the beginning of the MainApp
class. We'll also add a constructor that creates
some sample data and a public getter method:
MainApp.java
// ... AFTER THE OTHER VARIABLES ...
/**
* The data as an observable list of {@link Person}.
*/
private ObservableList< Person > personData = FXCollections.observableArrayList();
/**
* Constructor
*/
public MainApp() {
// Add some sample data
personData.add(new Person("Hans", "Muster"));
personData.add(new Person("Ruth", "Mueller"));
personData.add(new Person("Heinz", "Kurz"));
personData.add(new Person("Cornelia", "Meier"));
personData.add(new Person("Werner", "Meyer"));
personData.add(new Person("Lydia", "Kunz"));
personData.add(new Person("Anna", "Best"));
personData.add(new Person("Stefan", "Meier"));
personData.add(new Person("Martin", "Mueller"));
}
/**
* Returns the data as an observable list of {@link Person}.
*/
public ObservableList< Person > getPersonData() {
return personData;
}
// ... THE REST OF THE CLASS ...
The PersonOverviewController
We have our model and view. Let's get our data into our table. We'll need a controller for our PersonOverview.fxml
to act as a "middleman" for the model and view.
Create a normal Java class inside the view
package called PersonOverviewController
.
Note: We must put the class inside the same package as PersonOverview.fxml
, otherwise the SceneBuilder won't be able to find it.
We will add some instance variables that give us access to the table and the labels inside the view. The fields and some methods have a special @FXML
annotation. This is necessary in order for the .fxml
file to have access to private fields and private methods. After we have everything set up in the .fxml
file, the application will automatically fill the variables when the .fxml
file is loaded.
So let's add the following code:
Note: Remember to always use the javafx
imports, NOT awt
or swing
.
PersonOverviewController.java
package seedu.address.view;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import seedu.address.MainApp;
import seedu.address.model.Person;
public class PersonOverviewController {
@FXML
private TableView< Person> personTable;
@FXML
private TableColumn< Person, String> firstNameColumn;
@FXML
private TableColumn< Person, String> lastNameColumn;
@FXML
private Label firstNameLabel;
@FXML
private Label lastNameLabel;
@FXML
private Label streetLabel;
@FXML
private Label postalCodeLabel;
@FXML
private Label cityLabel;
@FXML
private Label birthdayLabel;
// Reference to the main application.
private MainApp mainApp;
/**
* The constructor. It is called before the initialize() method.
*/
public PersonOverviewController() {
}
/**
* Initializes the controller class. This method is automatically called
* after the fxml file has been loaded.
*/
@FXML
private void initialize() {
// Initialize the person table with the two columns.
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
}
/**
* Is called by the main application to give a reference back to itself.
*/
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
// Add observable list data to the table
personTable.setItems(mainApp.getPersonData());
}
}
Explanations
- All fields and methods where the
.fxml
file needs access must be annotated with@FXML
.- Actually, only if they are private, but it's better to have them private and mark them with the annotation!
- The
initialize()
method is automatically called after the.fxml
file has been loaded. At this time, all the FXML fields should have been initialized already. - The
setCellValueFactory(...)
that we set on the table colums are used to determine which field inside thePerson
objects should be used for the particular column. The arrow->
indicates that we're using a Java 8 feature called Lambdas. (Another option would be to use aPropertyValueFactory
, but this is not type-safe).
Note:
We're only using StringProperty
values for our table columns in this example. When you want to use IntegerProperty
or DoubleProperty
, the setCellValueFactory(...)
must have
an additional asObject()
:
myIntegerColumn.setCellValueFactory(cellData ->
cellData.getValue().myIntegerProperty().asObject());
This is necessary because of a bad design decision of JavaFX (see this discussion for more details).
Connecting MainApp with the PersonOverviewController
The setMainApp(...)
method must be called by the MainApp
class. This gives us a way to access the MainApp
object and get the list of Persons
and other things. Add the following
three lines to showPersonOverview()
the method:
MainApp.java - additional lines to add to showPersonOverview() method
// (.... root layout statement goes here ....)
// Give the controller access to the main app.
PersonOverviewController controller = loader.getController();
controller.setMainApp(this);
// (.... catch statement goes here ....)
Your showPersonOverview()
method in MainApp
should now look like this:
MainApp.java - new showPersonOverview() method
/**
* Shows the person overview inside the root layout.
*/
public void showPersonOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
AnchorPane personOverview = loader.load();
// Set person overview into the center of root layout.
rootLayout.setCenter(personOverview);
// Give the controller access to the main app.
PersonOverviewController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
Hook the View to the controller
We're almost there! But one thing is missing: We haven't told our PersonOverview.fxml
file which controller to use, and which element should match to which field inside the controller.
- Open
PersonOverview.fxml
with the SceneBuilder. - Open the Controller group on the left side (just below Hierarchy), and select the
seedu.address.view.PersonOverviewController
as the controller class.
- Select the
TableView
in the Hierarchy group. - In the Inspector view, under the Code group, set 'fx:id' to
personTable
.
- Do the same for the table columns. Select
firstNameColumn
andlastNameColumn
for the 'fx:id' respectively. - For each label in the second column of the grid pane, choose the corresponding 'fx:id'.
- Save the
.fxml
file.
Opening up the PersonOverviewController
to JavaFx
If you try and run the application now, you will encounter the following error:
javafx.fxml.LoadException: ...
...
Caused by: java.lang.IllegalAccessException: class javafx.fxml.FXMLLoader$ValueElement (in module javafx.fxml) cannot access class seedu.address.view.PersonOverviewController (in module AddressApp) because module AddressApp does not export seedu.address.view to module javafx.fxml
This is because JavaFx is unable to access our PersonOverviewController
class.
To fix this, add this line of code to src/module-info.java
:
module AddressApp {
...
opens seedu.address.view;
}
The file should now look something like this:
module-info.java
module AddressApp {
requires javafx.graphics;
requires javafx.fxml;
requires javafx.controls;
exports seedu.address;
opens seedu.address.view;
}
Start the Application
When you start your application now, you should see something like this:
Congratulations! The application now shows the list of Person
s in the view!
You may notice that selecting a person in the TableView
does nothing to the labels at the right side. That is because the user interaction portion has not been programmed yet, which we will cover in the next part
of the tutorial.
Evidence:
Acceptable: Using JavaFX in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-JavaFX]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
🅿️ Project
W7.9
Can do small changes to an existing software
Covered by the 'Product' component of v1.1:
Tutorial 7
An activity to do during tutorial:
Consider the code below:
class Person{
Tag tag;
String name;
Person(String personName, String tagName){
name = personName;
tag = new Tag(tagName);
}
}
class Tag{
Tag(String value){
//...
}
}
class PersonList{
void addPerson(Person p){
//...
}
}
Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:
PersonList personList = new PersonList();
while (hasRoom){
Person p = new Person("Adam", "friend");
personList.addPerson(p);
}
After that, draw a class diagram to match the code. Then, draw an object diagram to show the state of the objects after the code has executed one loop.
Here are some sample diagrams to use as references:
Questions to discuss during tutorial:
Explain the interactions depicted in this sequence diagram.
First, the createParser()
method of an existing ParserFactory
object is called. Then, ...
W7.1f
Can draw basic sequence diagrams
Design → Modelling → Modelling Behaviors
Sequence Diagrams - Basic
Explain in your own words the interactions illustrated by this Sequence Diagram:
Consider the code below:
class Person{
Tag tag;
String name;
Person(String personName, String tagName){
name = personName;
tag = new Tag(tagName);
}
}
class Tag{
Tag(String value){
//...
}
}
class PersonList{
void addPerson(Person p){
//...
}
}
Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:
PersonList personList = new PersonList();
while (hasRoom){
Person p = new Person("Adam", "friend");
personList.addPerson(p);
}
Find notation mistakes in the sequence diagram below:
Evidence:
Consider the code below:
class Person{
Tag tag;
String name;
Person(String personName, String tagName){
name = personName;
tag = new Tag(tagName);
}
}
class Tag{
Tag(String value){
//...
}
}
class PersonList{
void addPerson(Person p){
//...
}
}
Draw a sequence diagram to illustrate the object interactions that happen in the code snippet below:
PersonList personList = new PersonList();
while (hasRoom){
Person p = new Person("Adam", "friend");
personList.addPerson(p);
}
Submission: Show during tutorial.
W7.2a
Can explain APIs
Implementation → Reuse → APIs →
What
An Application Programming Interface (API) specifies the interface through which other programs can interact with a software component. It is a contract between the component and its clients.
A class has an API (e.g., API of the Java String
class, API of the Python str
class)
which is a collection of public methods that you can invoke to make use of the class.
The GitHub API is a collection of Web request formats GitHub server accepts and the corresponding responses. We can write a program that interacts with GitHub through that API.
When developing large systems, if you define the API of each components early, the development team can develop the components in parallel because the future behavior of the other components are now more predictable.
Choose the correct statements
- a. A software component can have an API.
- b. Any method of a class is part of its API.
- c. Private methods of a class are not part of its API.
- d. The API forms the contract between the component developer and the component user.
- e. Sequence diagrams can be used to show how components interact with each other via APIs.
(a) (c) (d) (e)
Explanation: (b) is incorrect because private methods cannot be a part of the API
Defining component APIs early is useful for developing components in parallel.
True
Explanation: Yes, once we know the precise behavior expected of each component, we can start developing them in parallel.
Evidence:
Know the API of the AddressBook component you are in charge of and the APIs of the other components your component depends on.
W7.3f
Can draw intermediate-level sequence diagrams
Design → Modelling → Modelling Behaviors
Sequence Diagrams - Intermediate
What’s going on here?
- a.
Logic
object is executing a parallel thread. - b.
Logic
object is executing a loop. - c.
Logic
object is creating anotherLogic
instance. - d. One of
Logic
object’s methods is calling another of its methods. - e.
Minefield
object is calling a method ofLogic
.
(d)
Explain the interactions depicted in this sequence diagram.
First, the createParser()
method of an existing ParserFactory
object is called. Then, ...
Draw a sequence diagram to represent this code snippet.
if (isFirstPage) {
new Quote().print();
}
The Quote
class:
class Quote{
String q;
Quote(){
q = generate();
}
String generate(){
// ...
}
void print(){
System.out.println(q);
}
}
- Show
new Quote().print();
as two method calls. - As the created Quote object is not assigned to a variable, it can be considered as 'deleted' soon after its
print()
method is called.
Evidence:
Explain the interactions depicted in this sequence diagram.
First, the createParser()
method of an existing ParserFactory
object is called. Then, ...
- Explain the sequence diagrams given [AddressBook Level4: Developer Guide]
- Add more sequence diagrams to project documentation (to be done in future weeks)
W7.4b
Can use logging
Implementation → Error Handling → Logging →
How
Most programming environments come with logging systems that allow sophisticated forms of logging. They have features such as the ability to enable and disable logging easily or to change the logging
This sample Java code uses Java’s default logging mechanism.
First, import the relevant Java package:
import java.util.logging.*;
Next, create a Logger
:
private static Logger logger = Logger.getLogger("Foo");
Now, you can use the Logger
object to log information. Note the use of
WARNING
so that log messages specified as INFO
level (which is a lower level than WARNING
)
will not be written to the log file at all.
// log a message at INFO level
logger.log(Level.INFO, "going to start processing");
//...
processInput();
if(error){
//log a message at WARNING level
logger.log(Level.WARNING, "processing error", ex);
}
//...
logger.log(Level.INFO, "end of processing");
Tutorials:
- Java Logging API - Tutorial -- A tutorial by Lars Vogella
- Java Logging Tutorial -- An alternative tutorial by Jakob Jenkov
- A video tutorial by SimplyCoded:
Best Practices:
- 10 Tips for Proper Application Logging -- by Tomasz Nurkiewicz
- What each logging level means -- conventions recommended by Apache Project
Evidence:
Use of logging in the code you have written in the module project or elsewhere.
W7.5b
Can use assertions
Implementation → Error Handling → Assertions →
How
Use the assert
keyword to define assertions.
This assertion will fail with the message x should be 0
if x
is not 0 at this point.
x = getX();
assert x == 0 : "x should be 0";
...
Assertions can be disabled without modifying the code.
java -enableassertions HelloWorld
(or java -ea HelloWorld
) will run HelloWorld
with assertions enabled while java -disableassertions HelloWorld
will run it without verifying assertions.
Java disables assertions by default. This could create a situation where you think all assertions are being verified as true
while in fact they are not being verified at all. Therefore, remember
to enable assertions when you run the program if you want them to be in effect.
💡 Enable assertions in Intellij (how?) and get an assertion to fail temporarily (e.g. insert an assert false
into the code temporarily) to confirm assertions are being verified.
Java assert
vs JUnit assertions: They are similar in purpose but JUnit assertions are more powerful and customized for testing. In addition, JUnit assertions
are not disabled by default. We recommend you use JUnit assertions in test code and Java assert
in functional code.
Tutorials:
- Java Assertions -- a simple tutorial from javatpoint.com
- Programming with Assertions (first half) -- a more detailed tutorial from Oracle
Best practices:
- Programming with Assertions (second half) -- from Oracle (also listed above as a tutorial) contains some best practices towards the end of the article.
Evidence:
Explain assertions in AddressBook-Level4 code.
W7.5c
Can use assertions optimally
Implementation → Error Handling → Assertions →
When
It is recommended that assertions be used liberally in the code. Their impact on performance is considered low and worth the additional safety they provide.
Do not use assertions to do work because assertions can be disabled. If not, your program will stop working when assertions are not enabled.
The code below will not invoke the writeFile()
method when assertions are disabled. If that method is performing some work that is necessary for your program, your program will not work correctly when assertions are disabled.
...
assert writeFile() : "File writing is supposed to return true";
Assertions are suitable for verifying assumptions about Internal Invariants, Control-Flow Invariants, Preconditions, Postconditions, and Class Invariants. Refer to [Programming with Assertions (second half)] to learn more.
Exceptions and assertions are two complementary ways of handling errors in software but they serve different purposes. Therefore, both assertions and exceptions should be used in code.
- The raising of an exception indicates an unusual condition created by the user (e.g. user inputs an unacceptable input) or the environment (e.g., a file needed for the program is missing).
- An assertion failure indicates the programmer made a mistake in the code (e.g., a null value is returned from a method that is not supposed to return null under any circumstances).
A Calculator program crashes with an ‘assertion failure’ message when you try to find the square root of a negative number.
(c)
Explanation: An assertion failure indicates a bug in the code. (b) is not acceptable because of the word "terminated". The application should not fail at all for this input. But it could have used an exception to handle the situation internally.
Which statements are correct?
- a. Use assertions to indicate the programmer messed up; Use exceptions to indicate the user or the environment messed up.
- b. Use exceptions to indicate the programmer messed up; Use assertions to indicate the user or the environment messed up.
(a)
Evidence:
Give an example from the AddressBook-Level4 code where an exception is used and explain why an assertion is not suitable for that situation. Similarly, explain why an exception is not suitable for a place where AddressBook Level4 uses an assertion.
W7.6c
Can explain how exception handling is done
typically
Implementation → Error Handling → Exceptions →
How
Most languages allow code that encountered an "exceptional" situation to encapsulate details of the situation in an Exception object and throw/raise that object so that another piece of code can catch it and deal with it. This is especially useful when the code that encountered the unusual situation does not know how to deal with it.
The extract below from the -- Java Tutorial (with slight adaptations) explains how exceptions are typically handled.
When an error occurs at some point in the execution, the code being executed creates an exception object and hands it off to the runtime system. The exception object contains information about the error, including its type and the state of the program when the error occurred. Creating an exception object and handing it to the runtime system is called throwing an exception.
After a method throws an exception, the runtime system attempts to find something to handle it in the
The exception handler chosen is said to catch the exception. If the runtime system exhaustively searches all the methods on the call stack without finding an appropriate exception handler, the program terminates.
Advantages of exception handling in this way:
- The ability to propagate error information through the call stack.
- The separation of code that deals with 'unusual' situations from the code that does the 'usual' work.
Which are benefits of exceptions?
- a. Exceptions allow us to separate normal code from error handling code.
- b. Exceptions can prevent problems that happen in the environment.
- c. Exceptions allow us to handle in one location an error raised in another location.
(a) (c)
Explanation: Exceptions cannot prevent problems in the environment. They can only be used to handle and recover from such problems.
Evidence:
Acceptable: Some code you wrote that involves exception handling.
Suggested: Do the exercise in addressbook-level2: LO-Exceptions.
Submission:
- Options 1 (discouraged): Show the relevant code during the tutorial.
- Options 2 (preferred): Create a PR against Addressbook-Level2 by following the instructions below.
If you choose option 2, we recommend that you complete this week's Project Management LOs first; there are many ways to create PRs but we expect you to create PRs in a specific way, as specified in the LOs.
W7.7a
Can use Java8 streams
Tools → Java →
Streams: Basic
Java 8 introduced a number of new features (e.g. Lambdas, Streams) that are not trivial to learn but also extremely useful to know.
Here is an overview of new Java 8 features . (written by Benjamin Winterberg)
Tutorials:
- Java 8 Tutorial -- from tutorialspoint.com. 💡 Also provides a way to try out code online
- Tutorials from Oracle: [Lambdas][Streams]
A video tutorial by well-known Java coach Venkat Subramaniam
Evidence:
- Your code (can be toy examples) that use some Java 8 features.
- Explain some parts of [AddressBook - Level 4] code that use Java 8 features. e.g.
AddressBook#syncMasterTagListWith(Person)
W7.8a
Can use JavaFX to build a simple GUI
Tools → Java →
JavaFX: Basic
Adapted (with permissions) from Marco Jakob's JavaFX 8 tutorial.
JavaFx 9 Tutorial - Part 1: Scene Builder
Introduction
This tutorial will teach you how to create a new JavaFX application in IntelliJ, and to use the SceneBuilder to create a layouts for your application.
Prerequisites
- Latest Java JDK 9 (includes JavaFX 9)
- IntelliJ (2018.2 or later)
- SceneBuilder 8 (provided by Gluon as Oracle no longer ships the tool in binary form)
Do remember the installation path to SceneBuilder 8 as we will need it to configure IntelliJ in a later step.
IntelliJ Configurations
If this is the first time using IntelliJ, you need to tell IntelliJ where to find JDK 9 and SceneBuilder.
Configuring JDK 9
- On the Welcome screen, press
Configure
→Project Default
→Project Structure
.
- If you already have a project open, go to the Welcome screen by going to
File
→Close Project
.
- Under
Project SDK:
, pressNew...
→JDK
. - Select the directory that you install JDK on, and press
OK
. - Under
Project language level:
, select9 - Modules, private methods in interfaces etc.
.
- Press
OK
again.
Configuring Scene Builder
- On the Welcome screen, press
Configure
→Settings
. - On the left hand side, select
Languages & Frameworks
→JavaFX
- Under
Path to SceneBuilder:
, select the path to where SceneBuilder is installed (e.g.C:\Users\Admin\AppData\Local\SceneBuilder\SceneBuilder.exe
on Windows)
The JavaDocs will come in handy when writing your own JavaFX applications:
Additionally, Oracle also has a tutorial on JavaFX if you are interested.
Create a new JavaFX Project
- On the Welcome screen, press
Create New Project
.
- If you already have a project, you can create a new project by going
File
→New
→Project...
.
- On the left side, select
JavaFX
. Make sure that the Project SDK is set to9
andJavaFX Application
is selected.
- Press
Next
. - Give a name for the application (e.g.
AddressApp
), and specify a suitable location. - Press
Finish
. If prompted to create a new directory, just pressOK
.
Remove the sample
package and its content. We will manually create our own package and resources in our tutorial.
We will also have to set up the IDE further, so that warnings and errors show up correctly when working with Java 9:
- Go to the menu
File
→Project Structure
. - Under
Project language level:
, ensure that9 - Modules, private methods in interfaces etc.
is selected.
Create the Packages
In We will create a package for each of the component. Ensure that your Project pane is open (Alt+1). Right click on the src
folder, and select New
→ Package
:
seedu.address
- contains the controller classes (i.e. the part that deals with the business logic)seedu.address.model
- contains the model classes (i.e. the part that deals with data)seedu.address.view
- contains the views (i.e. the part that deals with presenting the data to the user)
In subsequent tutorials, our view
package will also contain some controllers that are directly related to a single view. We will call them view-controllers
.
Create the FXML Layout File
There are two ways to create the UI:
- Use FXML, which is an XML format.
- Programmatically create the interface in Java.
We will use FXML for most parts, so that we can separate the view and controller from each other. Furthermore, we are able to use the Scene Builder tool to edit our FXML file. That means we will not have to directly work with XML.
Right click on the view
package, and press New
→ FXML file
. For the file name, type PersonOverview.fxml
.
Design with Scene Builder
Right-click on PersonOverview.fxml
and choose Open with Scene Builder
. Now you should see the Scene Builder with just an AnchorPane (visible under Hierarchy on the left).
If IntelliJ prompts for a location of the SceneBuilder executable, make sure to point to where you install SceneBuilder.
- Select the
Anchor Pane
in your Hierarchy, and adjust the size under Layout (right side). (Pref Width: 600, Pref Height: 300)
- Add a
Split Pane (horizontal)
(underContainers
) by dragging it from the Library into the main area. Right-click theSplit Pane
in the Hierarchy view and selectFit to Parent
.
- Drag a
TableView
(underControls
in Library view) into the left side of theSplitPane
. Select theTableView
(not a Column) and set the following layout constraints in the Inspector to theTableView
. Inside anAnchorPane
you can always set anchors to the four borders (see this page for more information on Layouts).
-
Go to the menu
Preview
→Show Preview in Window
to see whether the layout configuration is done correctly. Try resizing the window. TheTableView
should resize together with the window as it is anchored to the borders. -
Change the column text (under Properties) to "First Name" and "Last Name".
- Select the
TableView
and chooseconstrainted-resize
for the 'Column Resize Policy'. This ensures that the columns will always fill up the entire available space.
- Add a
Label
on the right side with the text "Person Details:". Adjust the layout using anchors (Top: 5, Left: 5. Right: Blank, Bottom: Blank).
You can use the Search Bar at the top of Library/Inspector to find the respective controls/properties.
- Add a
GridPane
on the right side. Select it, and adjust its layout using anchors (Top: 30, Left: 5, Right: 5, Bottom: Blank).
- Add the following labels to the cells, such that the grid is of this form:
First Name | Label |
---|---|
Last Name | Label |
Street | Label |
City | Label |
Postal Code | Label |
Birthday | Label |
To add a row to the GridPane, select an existing row number, right click the row number and choose "Add Row Below".
- Add a
ButtonBar
at the bottom. Add three buttons to the bar ("New...", "Edit...","Delete"). Adjust the anchors so that it stays at the bottom right (Top: Blank, Left: Blank, Right: 10, Bottom: 5).
- Now you should see something like the following. Use the
Preview
menu to test its resizing behaviour.
- Save the
.fxml
file.
Create the Main Application
The PersonOverview.fxml
that we just created only contains the content of our entire application. We need another FXML for our root layout, which will contain a menu bar and wraps PersonOverview.fxml
.
- Inside IntelliJ, right click on the
view
package, and pressNew
→FXML file
. For the file name, typeRootLayout.fxml
. - Right-click on
RootLayout.fxml
and chooseOpen with Scene Builder
. - Delete the
AnchorPane
. We will use another pane for our root layout.
- Add
BorderPane
by dragging it from the Library view into the main area.
- Resize the
BorderPane
(Pref Width: 600, Pref Height: 400)
- Add a
MenuBar
into theinsert TOP
slot. We will not implement menu functionality for now.
The JavaFX Main Class
Now, we need to create the main Java class that starts up our application with the RootLayout.fxml
and adds the PersonOverview.fxml
in the center.
Right-click on your seedu.address
package, and choose New
→ JavaFXApplication
. For the class name, type MainApp
.
The generated MainApp.java
class extends from Application
and contains two methods. This is the basic structure that we need to start a JavaFX Application. The most important part for us is the start(Stage primaryStage)
method. It is automatically called when the application is launch()
from within the main()
method.
As you see, the start(...)
method receives a Stage
as parameter. The following graphic illustrates the structure of every JavaFX application:
Image Source: http://www.oracle.com
It's like a theater play: The Stage
is the main container which is usually a Window
with a border and the typical minimize, maximize and close buttons. Inside the Stage
you
add a Scene
which can, of course, be switched out by another Scene
. Inside the Scene
the actual JavaFX nodes like AnchorPane
, TextBox
, etc. are added.
See this page for more info on working with the JavaFX Scene Graph.
Open MainApp.java
and replace the code with the following:
MainApp.java
package seedu.address;
import java.io.IOException;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.scene.layout.AnchorPane;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;
public class MainApp extends Application {
private Stage primaryStage;
private BorderPane rootLayout;
@Override
public void start(Stage primaryStage) {
this.primaryStage = primaryStage;
this.primaryStage.setTitle("AddressApp");
initRootLayout();
showPersonOverview();
}
/**
* Initializes the root layout.
*/
public void initRootLayout() {
try {
// Load root layout from fxml file.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/RootLayout.fxml"));
rootLayout = loader.load();
// Show the scene containing the root layout.
Scene scene = new Scene(rootLayout);
primaryStage.setScene(scene);
primaryStage.show();
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Shows the person overview inside the root layout.
*/
public void showPersonOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
AnchorPane personOverview = loader.load();
// Set person overview into the center of root layout.
rootLayout.setCenter(personOverview);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Returns the main stage.
*/
public Stage getPrimaryStage() {
return primaryStage;
}
public static void main(String[] args) {
launch(args);
}
}
The various comments should give you some hints about what's going on.
Importing JavaFx 9
JavaFx 9 is a separate module from Java 9. We must ensure that the modules are imported correctly.
Notice that IntellJ will complain about the imported modules "not being in the module graph":
To fix this:
- Ensure that your Project pane is open (Alt+1). Right click on the
src
folder, and selectNew
→module-info.java
: - Add the following
requires
andexports
in order to import and export the modules correctly:
module AddressApp {
requires javafx.graphics;
requires javafx.fxml;
requires javafx.controls;
exports seedu.address;
}
Running the Application
If you run the application now (right click MainApp.java
and select Run MainApp.main()
), you should see something like this:
Possible Issues
If JavaFx fails to load PersonOverview.fxml
, you might get the following error message:
javafx.fxml.LoadException:
/.../AddressApp/out/production/AddressApp/seedu/address/view/PersonOverview.fxml:15
To solve this issue, open PersonOverview.fxml
normally in IntelliJ and ensure that there is no such attribute around:
fx:controller="seedu.address.view.PersonOverview"
JavaFx 9 tutorial - Part 2: Model and TableView
Introduction
In this tutorial, we will create the other parts of the application (mainly the model and the controller). We will also use ObservableList
and *Property
to bind our list of new model's Person
s,
and their individual details, to the view, with the controller as the "middleman".
Create the Model class
We need a model class in order to hold information about the people in our address book. Add a new class to the model
package (seedu.address.model
) called Person
. The Person
class will encapsulate the details of an individual person such as name, address and birthday. Add the following code to the class. The JavaFX specifics will be explained after the code snippet.
Person.java
package seedu.address.model;
import java.time.LocalDate;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Model class for a Person.
*/
public class Person {
private final StringProperty firstName;
private final StringProperty lastName;
private final StringProperty street;
private final IntegerProperty postalCode;
private final StringProperty city;
private final ObjectProperty< LocalDate > birthday;
/**
* Default constructor.
*/
public Person() {
this(null, null);
}
/**
* Constructor with some initial data.
*/
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(firstName);
this.lastName = new SimpleStringProperty(lastName);
// Some initial dummy data, just for convenient testing.
this.street = new SimpleStringProperty("some street");
this.postalCode = new SimpleIntegerProperty(1234);
this.city = new SimpleStringProperty("some city");
this.birthday = new SimpleObjectProperty< LocalDate >(LocalDate.of(1999, 2, 21));
}
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String firstName) {
this.firstName.set(firstName);
}
public StringProperty firstNameProperty() {
return firstName;
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String lastName) {
this.lastName.set(lastName);
}
public StringProperty lastNameProperty() {
return lastName;
}
public String getStreet() {
return street.get();
}
public void setStreet(String street) {
this.street.set(street);
}
public StringProperty streetProperty() {
return street;
}
public int getPostalCode() {
return postalCode.get();
}
public void setPostalCode(int postalCode) {
this.postalCode.set(postalCode);
}
public IntegerProperty postalCodeProperty() {
return postalCode;
}
public String getCity() {
return city.get();
}
public void setCity(String city) {
this.city.set(city);
}
public StringProperty cityProperty() {
return city;
}
public LocalDate getBirthday() {
return birthday.get();
}
public void setBirthday(LocalDate birthday) {
this.birthday.set(birthday);
}
public ObjectProperty< LocalDate > birthdayProperty() {
return birthday;
}
}
Explanations
- With JavaFX, it is common to use
*Property
for all fields of a model class. AProperty
allows us, for example, to automatically be notified when thelastName
or any other variable is changed. This helps us keep the view in sync with the data. - To learn more about
*Property
, refer to Using JavaFX Properties and Binding LocalDate
, the type that we are using forbirthday
, is part of the new Date and Time API since JDK 8
A List of Persons
The main data that our application manages is simply a bunch of persons. Let's create a list of Person
objects inside the MainApp
class. All other controller classes will later get access to that
central list inside the MainApp
.
ObservableList
We are working with JavaFX view classes that need to be informed about any changes made to the list of persons. This is important, since otherwise the view would not be in sync with the data. For this purpose, JavaFX introduces some new Collection classes.
Among all these collections, we need the ObservableList
. To create a new ObservableList
, add the following code at the beginning of the MainApp
class. We'll also add a constructor that
creates some sample data and a public getter method:
MainApp.java
// ... AFTER THE OTHER VARIABLES ...
/**
* The data as an observable list of {@link Person}.
*/
private ObservableList< Person > personData = FXCollections.observableArrayList();
/**
* Constructor
*/
public MainApp() {
// Add some sample data
personData.add(new Person("Hans", "Muster"));
personData.add(new Person("Ruth", "Mueller"));
personData.add(new Person("Heinz", "Kurz"));
personData.add(new Person("Cornelia", "Meier"));
personData.add(new Person("Werner", "Meyer"));
personData.add(new Person("Lydia", "Kunz"));
personData.add(new Person("Anna", "Best"));
personData.add(new Person("Stefan", "Meier"));
personData.add(new Person("Martin", "Mueller"));
}
/**
* Returns the data as an observable list of {@link Person}.
*/
public ObservableList< Person > getPersonData() {
return personData;
}
// ... THE REST OF THE CLASS ...
The PersonOverviewController
We have our model and view. Let's get our data into our table. We'll need a controller for our PersonOverview.fxml
to act as a "middleman" for the model and view.
Create a normal Java class inside the view
package called PersonOverviewController
.
Note: We must put the class inside the same package as PersonOverview.fxml
, otherwise the SceneBuilder won't be able to find it.
We will add some instance variables that give us access to the table and the labels inside the view. The fields and some methods have a special @FXML
annotation. This is necessary in order for the .fxml
file to have access to private fields and private methods. After we have everything set up in the .fxml
file, the application will automatically fill the variables when the .fxml
file is loaded.
So let's add the following code:
Note: Remember to always use the javafx
imports, NOT awt
or swing
.
PersonOverviewController.java
package seedu.address.view;
import javafx.fxml.FXML;
import javafx.scene.control.Label;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import seedu.address.MainApp;
import seedu.address.model.Person;
public class PersonOverviewController {
@FXML
private TableView< Person> personTable;
@FXML
private TableColumn< Person, String> firstNameColumn;
@FXML
private TableColumn< Person, String> lastNameColumn;
@FXML
private Label firstNameLabel;
@FXML
private Label lastNameLabel;
@FXML
private Label streetLabel;
@FXML
private Label postalCodeLabel;
@FXML
private Label cityLabel;
@FXML
private Label birthdayLabel;
// Reference to the main application.
private MainApp mainApp;
/**
* The constructor. It is called before the initialize() method.
*/
public PersonOverviewController() {
}
/**
* Initializes the controller class. This method is automatically called
* after the fxml file has been loaded.
*/
@FXML
private void initialize() {
// Initialize the person table with the two columns.
firstNameColumn.setCellValueFactory(cellData -> cellData.getValue().firstNameProperty());
lastNameColumn.setCellValueFactory(cellData -> cellData.getValue().lastNameProperty());
}
/**
* Is called by the main application to give a reference back to itself.
*/
public void setMainApp(MainApp mainApp) {
this.mainApp = mainApp;
// Add observable list data to the table
personTable.setItems(mainApp.getPersonData());
}
}
Explanations
- All fields and methods where the
.fxml
file needs access must be annotated with@FXML
.- Actually, only if they are private, but it's better to have them private and mark them with the annotation!
- The
initialize()
method is automatically called after the.fxml
file has been loaded. At this time, all the FXML fields should have been initialized already. - The
setCellValueFactory(...)
that we set on the table colums are used to determine which field inside thePerson
objects should be used for the particular column. The arrow->
indicates that we're using a Java 8 feature called Lambdas. (Another option would be to use aPropertyValueFactory
, but this is not type-safe).
Note:
We're only using StringProperty
values for our table columns in this example. When you want to use IntegerProperty
or DoubleProperty
, the setCellValueFactory(...)
must
have an additional asObject()
:
myIntegerColumn.setCellValueFactory(cellData ->
cellData.getValue().myIntegerProperty().asObject());
This is necessary because of a bad design decision of JavaFX (see this discussion for more details).
Connecting MainApp with the PersonOverviewController
The setMainApp(...)
method must be called by the MainApp
class. This gives us a way to access the MainApp
object and get the list of Persons
and other things. Add the following
three lines to showPersonOverview()
the method:
MainApp.java - additional lines to add to showPersonOverview() method
// (.... root layout statement goes here ....)
// Give the controller access to the main app.
PersonOverviewController controller = loader.getController();
controller.setMainApp(this);
// (.... catch statement goes here ....)
Your showPersonOverview()
method in MainApp
should now look like this:
MainApp.java - new showPersonOverview() method
/**
* Shows the person overview inside the root layout.
*/
public void showPersonOverview() {
try {
// Load person overview.
FXMLLoader loader = new FXMLLoader();
loader.setLocation(MainApp.class.getResource("view/PersonOverview.fxml"));
AnchorPane personOverview = loader.load();
// Set person overview into the center of root layout.
rootLayout.setCenter(personOverview);
// Give the controller access to the main app.
PersonOverviewController controller = loader.getController();
controller.setMainApp(this);
} catch (IOException e) {
e.printStackTrace();
}
}
Hook the View to the controller
We're almost there! But one thing is missing: We haven't told our PersonOverview.fxml
file which controller to use, and which element should match to which field inside the controller.
- Open
PersonOverview.fxml
with the SceneBuilder. - Open the Controller group on the left side (just below Hierarchy), and select the
seedu.address.view.PersonOverviewController
as the controller class.
- Select the
TableView
in the Hierarchy group. - In the Inspector view, under the Code group, set 'fx:id' to
personTable
.
- Do the same for the table columns. Select
firstNameColumn
andlastNameColumn
for the 'fx:id' respectively. - For each label in the second column of the grid pane, choose the corresponding 'fx:id'.
- Save the
.fxml
file.
Opening up the PersonOverviewController
to JavaFx
If you try and run the application now, you will encounter the following error:
javafx.fxml.LoadException: ...
...
Caused by: java.lang.IllegalAccessException: class javafx.fxml.FXMLLoader$ValueElement (in module javafx.fxml) cannot access class seedu.address.view.PersonOverviewController (in module AddressApp) because module AddressApp does not export seedu.address.view to module javafx.fxml
This is because JavaFx is unable to access our PersonOverviewController
class.
To fix this, add this line of code to src/module-info.java
:
module AddressApp {
...
opens seedu.address.view;
}
The file should now look something like this:
module-info.java
module AddressApp {
requires javafx.graphics;
requires javafx.fxml;
requires javafx.controls;
exports seedu.address;
opens seedu.address.view;
}
Start the Application
When you start your application now, you should see something like this:
Congratulations! The application now shows the list of Person
s in the view!
You may notice that selecting a person in the TableView
does nothing to the labels at the right side. That is because the user interaction portion has not been programmed yet, which we will cover in the next
part of the tutorial.
Evidence:
Acceptable: Using JavaFX in any past project.
Suggested: Do the exercise in [Addressbook-Level3: LO-JavaFX]
Submission: Create a PR against Addressbook-Level3. Remember to use team ID (e.g. W09-2) in your PR name.
W7.9
Can do small changes to an existing software
Covered by the 'Product' component of v1.1:
Lecture 7
Slides: Uploaded on IVLE.