diff --git a/garnet/bin/setui/src/common.rs b/garnet/bin/setui/src/common.rs
new file mode 100644
index 0000000000000000000000000000000000000000..e6c60f9967dbb71763e2fb4c6d19fdfa7df8aad1
--- /dev/null
+++ b/garnet/bin/setui/src/common.rs
@@ -0,0 +1,25 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use failure::Error;
+use fidl_fuchsia_setui::*;
+use std::sync::mpsc::Sender;
+
+pub type ProcessMutation = dyn Fn(&Mutation) -> Result<Option<SettingData>, Error> + Send + Sync;
+
+/// Trait defining interface setting service uses to relay operations. Each
+/// adapter specifies the setting type it handles, which it will accept
+/// mutations for and relay updates.
+pub trait Adapter {
+    /// Returns the setting type this adapter is responsible for handling.
+    fn get_type(&self) -> SettingType;
+
+    /// Applies a mutation on the given adapter.
+    fn mutate(&mut self, mutation: &fidl_fuchsia_setui::Mutation) -> MutationResponse;
+
+    /// Registers a listener. The current value known to the client is passed
+    /// along. If an updated value is known, the sender is immediately invoked.
+    /// Otherwise, the sender is stored for later invocation.
+    fn listen(&self, sender: Sender<SettingData>, last_seen_data: Option<&SettingData>);
+}
diff --git a/garnet/bin/setui/src/fidl_clone.rs b/garnet/bin/setui/src/fidl_clone.rs
new file mode 100644
index 0000000000000000000000000000000000000000..4d80df338d3b5c53bce255a6f83bdcba5395a67c
--- /dev/null
+++ b/garnet/bin/setui/src/fidl_clone.rs
@@ -0,0 +1,118 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use fidl_fuchsia_setui::*;
+
+/// A placeholder for real cloning support in FIDL generated Rust code.
+/// TODO(QA-715): Remove
+pub trait FIDLClone {
+    fn clone(&self) -> Self;
+}
+
+impl FIDLClone for SettingData {
+    fn clone(&self) -> SettingData {
+        match self {
+            SettingData::StringValue(val) => {
+                return SettingData::StringValue(val.to_string());
+            }
+            SettingData::TimeZoneValue(val) => {
+                return SettingData::TimeZoneValue(val.clone());
+            }
+            SettingData::Connectivity(val) => {
+                return SettingData::Connectivity(val.clone());
+            }
+            SettingData::Intl(val) => {
+                return SettingData::Intl(val.clone());
+            }
+            SettingData::Wireless(val) => {
+                return SettingData::Wireless(val.clone());
+            }
+            SettingData::Account(val) => {
+                return SettingData::Account(val.clone());
+            }
+        }
+    }
+}
+
+impl FIDLClone for AccountSettings {
+    fn clone(&self) -> Self {
+        return AccountSettings { mode: self.mode };
+    }
+}
+
+impl FIDLClone for IntlSettings {
+    fn clone(&self) -> Self {
+        return IntlSettings {
+            locales: self.locales.clone(),
+            hour_cycle: self.hour_cycle,
+            temperature_unit: self.temperature_unit,
+        };
+    }
+}
+
+impl FIDLClone for ConnectedState {
+    fn clone(&self) -> ConnectedState {
+        return ConnectedState { reachability: self.reachability };
+    }
+}
+
+impl<T: FIDLClone> FIDLClone for Vec<T> {
+    fn clone(&self) -> Self {
+        return self.into_iter().map(FIDLClone::clone).collect();
+    }
+}
+
+impl<T: FIDLClone> FIDLClone for Option<Box<T>> {
+    fn clone(&self) -> Self {
+        match self {
+            None => None,
+            Some(val) => Some(Box::new(val.as_ref().clone())),
+        }
+    }
+}
+
+impl FIDLClone for TimeZoneInfo {
+    fn clone(&self) -> TimeZoneInfo {
+        return TimeZoneInfo { current: self.current.clone(), available: self.available.clone() };
+    }
+}
+
+impl FIDLClone for TimeZone {
+    fn clone(&self) -> Self {
+        return TimeZone {
+            id: self.id.clone(),
+            name: self.name.clone(),
+            region: self.region.clone(),
+        };
+    }
+}
+
+impl FIDLClone for WirelessState {
+    fn clone(&self) -> Self {
+        return WirelessState { wireless_networks: self.wireless_networks.clone() };
+    }
+}
+
+impl FIDLClone for WirelessNetwork {
+    fn clone(&self) -> Self {
+        return WirelessNetwork {
+            internal_id: self.internal_id,
+            ssid: self.ssid.clone(),
+            wpa_auth: self.wpa_auth,
+            wpa_cipher: self.wpa_cipher,
+            access_points: self.access_points.clone(),
+        };
+    }
+}
+
+impl FIDLClone for WirelessAccessPoint {
+    fn clone(&self) -> Self {
+        return WirelessAccessPoint {
+            bssid: self.bssid.clone(),
+            frequency: self.frequency,
+            rssi: self.rssi,
+            status: self.status,
+        };
+    }
+}
diff --git a/garnet/bin/setui/src/main.rs b/garnet/bin/setui/src/main.rs
index fbed5784a129ba24f9469be4ab57b39af03eab9f..ce0163714fe672a56c595c5b6f0ae6568001efae 100644
--- a/garnet/bin/setui/src/main.rs
+++ b/garnet/bin/setui/src/main.rs
@@ -4,6 +4,8 @@
 #![feature(async_await, await_macro, futures_api)]
 
 use {
+    crate::mutation::*,
+    crate::setting_adapter::SettingAdapter,
     failure::Error,
     fidl_fuchsia_setui::*,
     fuchsia_async as fasync,
@@ -15,6 +17,10 @@ use {
     std::sync::Arc,
 };
 
+mod common;
+mod fidl_clone;
+mod mutation;
+mod setting_adapter;
 mod setui_handler;
 
 fn main() -> Result<(), Error> {
@@ -26,6 +32,13 @@ fn main() -> Result<(), Error> {
     let mut fs = ServiceFs::new();
     let handler = Arc::new(SetUIHandler::new());
 
+    // TODO(SU-210): Remove once other adapters are ready.
+    handler.register_adapter(Box::new(SettingAdapter::new(
+        SettingType::Unknown,
+        Box::new(process_string_mutation),
+        None,
+    )));
+
     fs.dir("public").add_fidl_service(move |stream: SetUiServiceRequestStream| {
         let handler_clone = handler.clone();
         fx_log_info!("Connecting to setui_service");
diff --git a/garnet/bin/setui/src/mutation.rs b/garnet/bin/setui/src/mutation.rs
new file mode 100644
index 0000000000000000000000000000000000000000..0eee85710e2e7b37525ffadc4781b745058c47b6
--- /dev/null
+++ b/garnet/bin/setui/src/mutation.rs
@@ -0,0 +1,17 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+use failure::{format_err, Error};
+use fidl_fuchsia_setui::*;
+
+pub fn process_string_mutation(mutation: &Mutation) -> Result<Option<SettingData>, Error> {
+    if let Mutation::StringMutationValue(mutation_info) = mutation {
+        if mutation_info.operation == StringOperation::Update {
+            return Ok(Some(SettingData::StringValue(mutation_info.value.clone())));
+        }
+
+        return Ok(None);
+    } else {
+        return Err(format_err!("invalid error"));
+    }
+}
diff --git a/garnet/bin/setui/src/setting_adapter.rs b/garnet/bin/setui/src/setting_adapter.rs
new file mode 100644
index 0000000000000000000000000000000000000000..a039d522215144cc3ab361d39b89bff1d0691243
--- /dev/null
+++ b/garnet/bin/setui/src/setting_adapter.rs
@@ -0,0 +1,80 @@
+// Copyright 2019 The Fuchsia Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+use crate::common::*;
+use crate::fidl_clone::*;
+use fidl_fuchsia_setui::*;
+use std::sync::mpsc::Sender;
+use std::sync::Mutex;
+
+/// SettingAdapter provides a basic implementation of the Adapter trait,
+/// handling callbacks for hanging get interactions and keeping track of the
+/// latest values. Users of this implementation can provide a callback for
+/// processing mutations.
+pub struct SettingAdapter {
+    setting_type: SettingType,
+    latest_val: Option<SettingData>,
+    senders: Mutex<Vec<Sender<SettingData>>>,
+    mutation_process: Box<ProcessMutation>,
+}
+
+impl SettingAdapter {
+    pub fn new(
+        setting_type: SettingType,
+        mutation_processor: Box<ProcessMutation>,
+        default_value: Option<SettingData>,
+    ) -> SettingAdapter {
+        return SettingAdapter {
+            setting_type: setting_type,
+            latest_val: default_value,
+            senders: Mutex::new(vec![]),
+            mutation_process: mutation_processor,
+        };
+    }
+}
+
+impl Adapter for SettingAdapter {
+    fn get_type(&self) -> SettingType {
+        return self.setting_type;
+    }
+
+    fn mutate(&mut self, mutation: &fidl_fuchsia_setui::Mutation) -> MutationResponse {
+        let result = (self.mutation_process)(mutation);
+
+        match result {
+            Ok(Some(setting)) => {
+                self.latest_val = Some(setting.clone());
+
+                if let Ok(mut senders) = self.senders.lock() {
+                    while !senders.is_empty() {
+                        let sender_option = senders.pop();
+
+                        if let Some(sender) = sender_option {
+                            sender.send(setting.clone()).ok();
+                        }
+                    }
+                }
+            }
+            Ok(None) => {
+                self.latest_val = None;
+            }
+            _ => {
+                return MutationResponse { return_code: ReturnCode::Failed };
+            }
+        }
+
+        return MutationResponse { return_code: ReturnCode::Ok };
+    }
+
+    /// Listen
+    fn listen(&self, sender: Sender<SettingData>, _last_seen_data: Option<&SettingData>) {
+        if let Some(ref value) = self.latest_val {
+            sender.send(value.clone()).ok();
+        } else {
+            if let Ok(mut senders) = self.senders.lock() {
+                senders.push(sender);
+            }
+        }
+    }
+}
diff --git a/garnet/bin/setui/src/setui_handler.rs b/garnet/bin/setui/src/setui_handler.rs
index 179b556747d3e3c17c41d144cadefd1549f3db44..506f276eb5f506d82b88ee7d3b8db02392d9c83f 100644
--- a/garnet/bin/setui/src/setui_handler.rs
+++ b/garnet/bin/setui/src/setui_handler.rs
@@ -2,44 +2,112 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-use failure::Error;
+use crate::common::Adapter;
+use crate::fidl_clone::*;
+use failure::{format_err, Error};
 use fidl_fuchsia_setui::*;
+use fuchsia_syslog::fx_log_err;
 use futures::prelude::*;
+use std::collections::HashMap;
+use std::sync::mpsc::{channel, Sender};
+use std::sync::Arc;
+use std::sync::Mutex;
+use std::sync::RwLock;
 
-pub struct SetUIHandler {}
+use fuchsia_async as fasync;
+
+pub struct SetUIHandler {
+    // Tracks active adapters.
+    adapters: RwLock<HashMap<SettingType, Mutex<Box<dyn Adapter + Send + Sync>>>>,
+    // Provides bookkeeping for last value transmitted by a callback. Asynchronous
+    // to allow sharing between all callback closures.
+    last_seen_settings: Arc<RwLock<HashMap<SettingType, SettingData>>>,
+}
 
 /// SetUIHandler handles all API calls for the service. It is intended to be
 /// used as a single instance to service multiple streams.
 impl SetUIHandler {
-    /// In the future, will populate with supporting classes, such as adapters.
     pub fn new() -> SetUIHandler {
-        Self {}
+        Self {
+            adapters: RwLock::new(HashMap::new()),
+            last_seen_settings: Arc::new(RwLock::new(HashMap::new())),
+        }
+    }
+
+    /// Adds (or replaces) any existing adapter. Setting type mapping is
+    /// determined from the adapter's reported type.
+    pub fn register_adapter(&self, adapter: Box<dyn Adapter + Send + Sync>) {
+        self.adapters.write().unwrap().insert(adapter.get_type(), Mutex::new(adapter));
     }
 
     /// Asynchronous handling of the given stream. Note that we must consider the
     /// possibility of simultaneous active streams.
     pub async fn handle_stream(&self, mut stream: SetUiServiceRequestStream) -> Result<(), Error> {
+        // A single StreamSetting watcher is created for the lifetime of the stream.
+        // It is used for asynchronously processing updates to a setting type and
+        // tracking the current values.
+
         while let Some(req) = await!(stream.try_next())? {
-            await!(self.handle_request(req))?;
+            match req {
+                SetUiServiceRequest::Mutate { setting_type, mutation, responder } => {
+                    let mut response = self.mutate(setting_type, mutation);
+                    responder.send(&mut response)?;
+                }
+                SetUiServiceRequest::Watch { setting_type, responder } => {
+                    self.watch(setting_type, responder);
+                }
+                _ => {}
+            }
         }
 
         Ok(())
     }
 
-    /// Routes a given request to the proper handling function.
-    pub async fn handle_request(&self, req: SetUiServiceRequest) -> Result<(), fidl::Error> {
-        match req {
-            SetUiServiceRequest::Mutate { setting_type, mutation, responder } => {
-                let mut response = self.mutate(setting_type, mutation);
-                responder.send(&mut response)?;
+    fn watch(&self, setting_type: SettingType, responder: SetUiServiceWatchResponder) {
+        let (sender, receiver) = channel();
+
+        if self.listen(setting_type, sender).is_ok() {
+            // We clone here so the value can be moved into the closure below.
+            let last_seen_settings_clone = self.last_seen_settings.clone();
+
+            fasync::spawn(
+                async move {
+                    let data: SettingData = receiver.recv().unwrap();
+                    last_seen_settings_clone.write().unwrap().insert(setting_type, data.clone());
+                    responder
+                        .send(&mut SettingsObject {
+                            setting_type: setting_type,
+                            data: data.clone(),
+                        })
+                        .ok();
+                },
+            );
+        } else {
+            fx_log_err!("watch: no valid adapter for type");
+        }
+    }
+
+    /// The core logic behind listening to a setting is separate from the watch
+    /// method definition to facilitate testing.
+    fn listen(&self, setting_type: SettingType, sender: Sender<SettingData>) -> Result<(), Error> {
+        if let Some(adapter_lock) = self.adapters.read().unwrap().get(&setting_type) {
+            if let Ok(adapter) = adapter_lock.lock() {
+                adapter.listen(sender, self.last_seen_settings.read().unwrap().get(&setting_type));
+                return Ok(());
             }
-            _ => {}
         }
-        Ok(())
+
+        return Err(format_err!("cannot listen. non-existent adapter"));
     }
 
     /// Applies a mutation
-    fn mutate(&self, _setting_type: SettingType, _mutation: Mutation) -> MutationResponse {
+    fn mutate(&self, setting_type: SettingType, mutation: Mutation) -> MutationResponse {
+        if let Some(adapter_lock) = self.adapters.read().unwrap().get(&setting_type) {
+            if let Ok(mut adapter) = adapter_lock.lock() {
+                adapter.mutate(&mutation);
+            }
+        }
+
         MutationResponse { return_code: ReturnCode::Ok }
     }
 }
@@ -47,6 +115,8 @@ impl SetUIHandler {
 #[cfg(test)]
 mod tests {
     use super::*;
+    use crate::mutation::*;
+    use crate::setting_adapter::SettingAdapter;
 
     /// A basic test to exercise that basic functionality works. In this case, we
     /// mutate the unknown type, reserved for testing. We should always immediately
@@ -65,4 +135,51 @@ mod tests {
         assert_eq!(result, MutationResponse { return_code: ReturnCode::Ok });
     }
 
+    /// A test to verify the listen functionality. Note that we use the listen
+    /// method directly on the handler as we do not mock the FIDL interface.
+    #[test]
+    fn test_listen() {
+        // Test value to ensure
+        let test_val = "FooBar".to_string();
+        // Create handler and register test adapter
+        let handler = SetUIHandler::new();
+        handler.register_adapter(Box::new(SettingAdapter::new(
+            SettingType::Unknown,
+            Box::new(process_string_mutation),
+            None,
+        )));
+
+        // Introduce change to the adapter.
+        assert_eq!(
+            handler.mutate(
+                SettingType::Unknown,
+                fidl_fuchsia_setui::Mutation::StringMutationValue(StringMutation {
+                    operation: StringOperation::Update,
+                    value: test_val.clone()
+                }),
+            ),
+            MutationResponse { return_code: ReturnCode::Ok }
+        );
+
+        let (sender, receiver) = channel();
+
+        // Listen for change
+        assert!(handler.listen(SettingType::Unknown, sender).is_ok());
+
+        let listen_result = receiver.recv();
+
+        assert!(listen_result.is_ok());
+
+        let data = listen_result.unwrap();
+
+        // Ensure value matches original change.
+        match data {
+            SettingData::StringValue(string_val) => {
+                assert_eq!(string_val, test_val.clone());
+            }
+            _ => {
+                panic!("Did not receive back expected SettingData type");
+            }
+        }
+    }
 }