Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Android Build a Weather App (2015) Hooking Up the Model to the View Setting the Weather Icon

Build a Weather App code problems (my solution)

I am posting this to help others who are having problems with this section of the app/videos. I had a lot of problems and couldn't find any answers at the time of this posting.

Here are the field variables that Ben was hoping to save with butterknife. Notice I also removed everything that has to do with buttknife including (1) imports, (2) gradle, (3) MainActivity code.

public class MainActivity extends AppCompatActivity {

    public static final String TAG = MainActivity.class.getSimpleName();

    private CurrentWeather mCurrentWeather;
    private TextView mTemperatureLabel;
    private TextView mTimeLabel;
    private TextView mHumidityValue;
    private TextView mPrecipValue;
    private TextView mSummaryLabel;
    private ImageView mIconImageView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTemperatureLabel = (TextView) findViewById(R.id.temperatureLabel);
        mTimeLabel = (TextView) findViewById(R.id.timeLabel);
        mHumidityValue = (TextView) findViewById(R.id.humidityValue);
        mPrecipValue = (TextView) findViewById(R.id.precipValue);
        mSummaryLabel = (TextView) findViewById(R.id.summaryLabel);
        mIconImageView = (ImageView) findViewById(R.id.iconImageView);

Inside of the updateDisplay() method I used String.valueOf() because contacenating an empty string was giving me errors.

    private void updateDisplay() {
        mTemperatureLabel.setText(String.valueOf(mCurrentWeather.getTemperature()));
        mTimeLabel.setText(String.format("At %s it will be", String.valueOf(mCurrentWeather.getFormattedTime())));
        mHumidityValue.setText(String.valueOf(mCurrentWeather.getHumidity()));
        mPrecipValue.setText(String.format("%s", String.valueOf(mCurrentWeather.getPrecipChance()) + "%"));
        mSummaryLabel.setText(String.valueOf(mCurrentWeather.getSummary()));

        Drawable drawable = ResourcesCompat.getDrawable(getResources(), mCurrentWeather.getIconId(), null);
        mIconImageView.setImageDrawable(drawable);
    }

I am no expert, but this code works without giving me errors and I just wanted to help others so we can complete this weather app!

Edward C. Young
Edward C. Young
10,323 Points

I hope my rather long answer below explains why you were having issues. I had these same issues, but took a different approach because I decided to "fight the IDE."

1 Answer

Edward C. Young
Edward C. Young
10,323 Points

Butterknife boilerplate code has nothing to do with Concatenating Strings. Did you include it properly? See the Download heading at the ButterKnife GitHub Page. All ButterKnife does is use Macros to insert reusable code. I used ButterKnife in my code and it worked great. As I explain, I'll provide snippets from my working app, for example, the Butterknife code:

    @BindView(R.id.timeLabel) TextView mTimeLabel;
    @BindView(R.id.temperatureLabel) TextView mTemperatureLabel;
    @BindView(R.id.humidityValue) TextView mHumidityValue;
    @BindView(R.id.precipValue) TextView mPrecipValue;
    @BindView(R.id.summaryLabel) TextView mSummaryLabel;
    @BindView(R.id.iconImageView) ImageView mIconImageView;
    @BindView(R.id.refreshImageView) ImageView mRefreshImageView;
    @BindView(R.id.updateInfoProgressBar) ProgressBar mUpdateInfoProgressBar;

Android's OS principle is to support multiple languages out of the box(Localization). Adding an empty String to a variable does indeed dirty convert it to a String, but the result is a Hard Coded String. Our newer versions of Android Studio include checks and code completion for Localization.

In plain java, there is nothing wrong with this approach, but note that "The Current Temperature is " + mTemperature; only displays correctly if I'm in an English speaking country. Android Studio will spit out Warnings in layout code, and Errors in classes where Strings are built to enforce this principle regarding hard-coded strings. The video doesn't focus on this because Ben would rather teach you the General Principles of coding the app, not the rule enforcement of the IDE. Instead of refactoring the code the way you did, consider adding String Resources like the IDE is telling you. I believe that valueOf() will still result in a hard-coded String, as it can't be translated inside the method. Fixing what the Studio is telling you ensures that the app is ready to exist in the Android App store. It's a lot easier to fix now than it is to fix when you have published it, and a whole lot of International users blow up your inbox complaining that they can't understand what you wrote in broken English.

private void updateDisplay() {

        // Temperature
        String temperatureFormat = getResources().getString(R.string.integerOutput);
        int currentTemperature = mCurrentWeather.getTemperature();
        String temperatureLabel = String.format(temperatureFormat,currentTemperature);
        mTemperatureLabel.setText(temperatureLabel);

        // Time
        String timeFormat = getString(R.string.timeString);
        String currentTime = mCurrentWeather.getFormattedTime();
        String timeLabel = String.format(timeFormat,currentTime);
        mTimeLabel.setText(timeLabel);

        // Humidity
        String humidityFormat = getString(R.string.percentageOutput);
        int currentHumidity = mCurrentWeather.getHumidity();
        String humidityLabel = String.format(humidityFormat,currentHumidity);
        mHumidityValue.setText(humidityLabel);

        //Precipitation Chance
        String precipitationFormat = getString(R.string.percentageOutput);
        int currentPrecipitationChance = mCurrentWeather.getPrecipitationChance();
        String precipitationLabel = String.format(precipitationFormat, currentPrecipitationChance);
        mPrecipValue.setText(precipitationLabel);

        // Summary Text
        mSummaryLabel.setText(mCurrentWeather.getSummary());

        // Icon Update
        Drawable drawable = ResourcesCompat.getDrawable(getResources(), mCurrentWeather.getIconId(), null);
        mIconImageView.setImageDrawable(drawable);
    }

Notice the ones that use concatenated Strings (Temperature, Time , Humidity, and Precipitation) are using String Formatters from a String Resource File, and that none of my code is Inlined(I've successfully split code from presentation). If I want to change the presentation, all I need to do is update strings.xml. Note that the Summary isn't translated because I believe that's translated by the forcast.io service. I'll explain what all that is after I post my String Resource:

<resources>
    <string name="app_name">Stormy</string>
    <string name="error_title">Oops, Sorry!</string>
    <string name="error_message">There was an error.  Please try again.</string>
    <string name="error_ok_button_text">OK</string>
    <string name="network_unavailable_message">Network is Unavailable!</string>
    <string name="tempPreview">--</string>
    <string name="degree_symbol">Degree Symbol</string>
    <string name="timePreview"></string>
    <string name="defaultLocation">Alcatraz Island, CA</string>
    <string name="defaultLocationForScreenReaders">Current Weather Location</string>
    <string name="humidityColumnHeader">HUMIDITY</string>
    <string name="humidityPreview">--</string>
    <string name="precipColumnHeader">RAIN/SNOW?</string>
    <string name="precipPreview">--</string>
    <string name="summaryPreview">Getting Current Weather…</string>
    <string name="timeString">At %1$s it will be</string>
    <string name="refreshButton">Refresh Button</string>
    <string name="percentageOutput">%d%%</string>
    <string name="integerOutput">%d</string>
</resources>

Taking a close look at the strings.xml file, notice the layout items, i.e. humidityPreview. What if I wanted to use +++ instead of ---? Instead of editing my working layout, I edit the Strings file, as the pointer in the layout file will always point to the String Resource. Now, the main purpose of a String Resource File is to provide a translation mechanism, known as Localization. If I had to build a layout for each language, I'd waste time, and before localization, that's what some programmers did. Instead, use the free variant of the site I just linked to translate your app, At the time I wrote this, I had no idea there was a service like this. Note that the French app localization, is now stored in res/values-fr/strings.xml. To see this in action, see my layout:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.carterswebs.stormy.MainActivity"
    android:background="#FFB300"
    android:contentDescription="@string/degree_symbol"
    android:id="@+id/activityMasterLayout">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/tempPreview"
        android:id="@+id/temperatureLabel"
        android:layout_centerVertical="true"
        android:layout_centerHorizontal="true"
        android:textColor="@android:color/white"
        android:textSize="150sp"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/degreeImageView"
        android:layout_alignTop="@+id/temperatureLabel"
        android:layout_toRightOf="@+id/temperatureLabel"
        android:layout_toEndOf="@+id/temperatureLabel"
        android:contentDescription="@string/degree_symbol"
        android:layout_marginTop="50dp"
        android:src="@drawable/degree"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/timePreview"
        android:id="@+id/timeLabel"
        android:textColor="#80ffffff"
        android:textSize="18sp"
        android:layout_above="@+id/temperatureLabel"
        android:layout_centerHorizontal="true"/>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/defaultLocation"
        android:id="@+id/locationLabel"
        android:layout_above="@+id/temperatureLabel"
        android:layout_centerHorizontal="true"
        android:layout_marginBottom="60dp"
        android:textColor="@android:color/white"
        android:textSize="24sp"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/iconImageView"
        android:layout_alignBottom="@+id/locationLabel"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:src="@drawable/cloudy_night"
        android:contentDescription="@string/defaultLocationForScreenReaders"/>

    <LinearLayout
        android:orientation="horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/temperatureLabel"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="10dp"
        android:weightSum="100"
        android:id="@+id/containerForWeatherColumns"
        android:baselineAligned="false">

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="50"
            android:id="@+id/humidityColumn">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/humidityColumnHeader"
                android:id="@+id/humidityLabel"
                android:textColor="#80ffffff"
                android:gravity="center_horizontal"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/humidityPreview"
                android:id="@+id/humidityValue"
                android:textColor="@android:color/white"
                android:textSize="24sp"
                android:gravity="center_horizontal"/>
        </LinearLayout>

        <LinearLayout
            android:orientation="vertical"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:layout_weight="50"
            android:id="@+id/RainColumn">

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/precipColumnHeader"
                android:id="@+id/precipLabel"
                android:textColor="#80ffffff"
                android:gravity="center_horizontal"/>

            <TextView
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:text="@string/precipPreview"
                android:id="@+id/precipValue"
                android:textColor="@android:color/white"
                android:textSize="24sp"
                android:gravity="center_horizontal"/>
        </LinearLayout>
    </LinearLayout>

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/summaryPreview"
        android:id="@+id/summaryLabel"
        android:layout_below="@+id/containerForWeatherColumns"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="40dp"
        android:textColor="@android:color/white"
        android:textSize="18sp"
        android:gravity="center_horizontal"/>

    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/refreshImageView"
        android:layout_alignParentTop="true"
        android:layout_centerHorizontal="true"
        android:src="@drawable/refresh"
        android:contentDescription="@string/refreshButton"/>

    <ProgressBar
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/updateInfoProgressBar"
        android:layout_alignBottom="@+id/refreshImageView"
        android:layout_centerHorizontal="true"/>

</RelativeLayout>

Concerning the formatter Strings:

Do this in order:

  1. Build the Format String, i.e timeString, using the R class.
  2. Format the format string to include your desired output. This can also be done by inlining, but as a personal preference, I built a variable. Note that the format string can be used in multiple places. I converted Humidity and Precipitation to a percentage.
  3. Set the Text of the TextView using the output from the Formatter

Concerning the output formatters

  • %n, is the number of the variable you are trying to output. "HelloWorld, my name is %1 %2 I live at %3 in %4." You get the idea.
  • $ followed by the format type, s for strings, d for decimals and ints.

For more see Android SDK Quick Tip: Formatting Resource Strings. Looking at the bigger picture here, the items you cleaned up are build tools that are used to help you. Like you, I used to believe IDE's were overkill at times, but after working two years in a .NET environment, I've changed my outlook.